agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v24 04/15] Allow to prolong life span of transition tables until transaction end 672+ messages / 2 participants [nested] [flat]
* [PATCH v24 04/15] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 2 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d8890d2c74..c7a2bc3ed8 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3670,6 +3675,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 9ef7f6d768..0cfb0b3c5c 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Fri__29_Oct_2021_18_16_28_+0900_jlYRKjywLqhZ7oyk Content-Type: text/x-diff; name="v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v27 3/9] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 13cb516752..aa1da7b279 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3730,6 +3730,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3795,6 +3799,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3830,6 +3835,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3879,6 +3885,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4725,6 +4732,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4933,6 +4979,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5093,19 +5140,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -5117,6 +5164,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 194e8d5bc1..2e9f1ac8c7 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -261,6 +261,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Fri__22_Apr_2022_11_29_39_+0900_ZOAC7UMt5e8j1Nvx Content-Type: text/x-diff; name="v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v27 3/9] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 13cb516752..aa1da7b279 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3730,6 +3730,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3795,6 +3799,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3830,6 +3835,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3879,6 +3885,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4725,6 +4732,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4933,6 +4979,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5093,19 +5140,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -5117,6 +5164,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 194e8d5bc1..2e9f1ac8c7 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -261,6 +261,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Fri__22_Apr_2022_14_58_01_+0900_MN3L/o2YUVF2g4zw Content-Type: text/x-diff; name="v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v27-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 401baddbfc6..dc746bccc8e 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3844,6 +3844,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3909,6 +3913,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3957,6 +3962,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -4003,6 +4009,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4898,6 +4905,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5138,6 +5184,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5312,11 +5359,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5334,8 +5381,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__3_Jul_2026_19_11_16_+0900_CskxSIu_iGcDMEyM Content-Type: text/x-diff; name="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v39-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 1d9869973c0..e5dd915096d 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Fri__29_May_2026_23_14_17_+0900_Te0o73X2VqYK57Gd Content-Type: text/x-diff; name="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v37-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index b87b4b40d07..49fe531198a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3819,6 +3819,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3884,6 +3888,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3932,6 +3937,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* "old" transition table for UPDATE/DELETE, if any */ @@ -3978,6 +3984,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4873,6 +4880,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -5113,6 +5159,7 @@ AfterTriggerBeginXact(void) afterTriggers.firing_depth = 0; afterTriggers.batch_callbacks = NIL; afterTriggers.firing_batch_callbacks = false; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5287,11 +5334,11 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5309,8 +5356,33 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) qs->tables = NIL; list_free_deep(tables); - list_free_deep(qs->batch_callbacks); - qs->batch_callbacks = NIL; + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0c3d485abf4..f0f052cc759 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -269,6 +269,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.43.0 --Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ Content-Type: text/x-diff; name="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v38-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v34 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 170360edda..0e1b3faed7 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3754,6 +3754,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3819,6 +3823,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3854,6 +3859,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3903,6 +3909,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4782,6 +4789,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4992,6 +5038,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5152,19 +5199,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5181,6 +5228,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 8a5a9fe642..6718514d34 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.34.1 --Multipart=_Thu__11_Jul_2024_13_23_57_+0900_hKsU2G3a3BgS2FFs-- ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v33 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 58b7fc5bbd..bd4a2219a4 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3753,6 +3753,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3818,6 +3822,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3853,6 +3858,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3902,6 +3908,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4781,6 +4788,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4991,6 +5037,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5151,19 +5198,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5180,6 +5227,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 8a5a9fe642..6718514d34 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Tue__2_Jul_2024_17_03_11_+0900_6OrVBZxOB4o_k6A1 Content-Type: text/x-diff; name="v33-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Disposition: attachment; filename="v33-0002-Add-relisivm-column-to-pg_class-system-catalog.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v30 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index c344ff0944..364e3a53f7 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3761,6 +3761,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3826,6 +3830,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3861,6 +3866,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3910,6 +3916,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4787,6 +4794,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4997,6 +5043,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5157,19 +5204,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5186,6 +5233,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 8a5a9fe642..6718514d34 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Mon__4_Mar_2024_11_58_46_+0900_UaponF/qQhQrVCFt Content-Type: text/x-diff; name="v30-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v30-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v31 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 84494c4b81..0424ab9a29 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3729,6 +3729,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3794,6 +3798,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3829,6 +3834,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3878,6 +3884,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4755,6 +4762,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4965,6 +5011,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5125,19 +5172,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5154,6 +5201,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index cb968d03ec..768d6e1c0b 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Fri__29_Mar_2024_23_47_00_+0900_KGpmmDOIs1266Ib1 Content-Type: text/x-diff; name="v31-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v31-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v32 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 84494c4b81..0424ab9a29 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3729,6 +3729,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3794,6 +3798,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3829,6 +3834,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3878,6 +3884,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4755,6 +4762,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4965,6 +5011,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5125,19 +5172,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5154,6 +5201,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index cb968d03ec..768d6e1c0b 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Sun__31_Mar_2024_22_59_31_+0900_msknEviJj08_wgqO Content-Type: text/x-diff; name="v32-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v32-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v29 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 52177759ab..00b20f4b5b 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3742,6 +3742,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3807,6 +3811,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3842,6 +3847,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3891,6 +3897,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4768,6 +4775,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4978,6 +5024,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5138,19 +5185,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5167,6 +5214,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 430e3ca7dd..48a21c4c51 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Mon__28_Aug_2023_11_52_52_+0900_hj6L5h176QaSGtg7 Content-Type: text/x-diff; name="v29-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v29-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v29 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 52177759ab..00b20f4b5b 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3742,6 +3742,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3807,6 +3811,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3842,6 +3847,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3891,6 +3897,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4768,6 +4775,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4978,6 +5024,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5138,19 +5185,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5167,6 +5214,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 430e3ca7dd..48a21c4c51 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Mon__28_Aug_2023_16_05_30_+0900_b1OvQD_3A3ZMTGvj Content-Type: text/x-diff; name="v29-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v29-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v28 03/11] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 83 ++++++++++++++++++++++++++++++++-- src/include/commands/trigger.h | 2 + 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 4b295f8da5..354ea94777 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3742,6 +3742,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3807,6 +3811,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3842,6 +3847,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ /* @@ -3891,6 +3897,7 @@ static void TransitionTableAddTuple(EState *estate, TupleTableSlot *original_insert_tuple, Tuplestorestate *tuplestore); static void AfterTriggerFreeQuery(AfterTriggersQueryData *qs); +static void release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState origstate); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -4737,6 +4744,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4947,6 +4993,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -5107,19 +5154,19 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_upd_tuplestore; table->old_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_upd_tuplestore; table->new_upd_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->old_del_tuplestore; table->old_del_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); ts = table->new_ins_tuplestore; table->new_ins_tuplestore = NULL; if (ts) - tuplestore_end(ts); + release_or_prolong_tuplestore(ts, table->prolonged); if (table->storeslot) { TupleTableSlot *slot = table->storeslot; @@ -5136,6 +5183,34 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } +} + +/* + * Release the tuplestore, or append it to the prolonged tuplestores list. + */ +static void +release_or_prolong_tuplestore(Tuplestorestate *ts, bool prolonged) +{ + if (prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 430e3ca7dd..48a21c4c51 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -265,6 +265,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.25.1 --Multipart=_Thu__1_Jun_2023_23_59_09_+0900_/G5+8nG46.f1T42K Content-Type: text/x-diff; name="v28-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v28-0004-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v25 04/15] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 2 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 1a9c1ac290..0fcb1aa0b5 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3651,6 +3651,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3716,6 +3720,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3751,6 +3756,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -4529,6 +4535,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4723,6 +4768,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -4883,11 +4929,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -4899,6 +4963,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index e1271420e5..697a6f6d55 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -255,6 +255,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Fri__4_Feb_2022_01_48_06_+0900_N8BNZpfOR27sWrgY Content-Type: text/x-diff; name="v25-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patch" Content-Disposition: attachment; filename="v25-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v26 04/10] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 2 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index e08bd9a370..adc853a570 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3651,6 +3651,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3716,6 +3720,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3751,6 +3756,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -4539,6 +4545,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4733,6 +4778,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -4893,11 +4939,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -4909,6 +4973,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index e1271420e5..697a6f6d55 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -255,6 +255,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Mon__14_Mar_2022_19_12_17_+0900_E9HVp8j5QFkIIek. Content-Type: text/x-diff; name="v26-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patch" Content-Disposition: attachment; filename="v26-0003-Add-new-deptype-option-m-in-pg_depend-system-cat.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v23 04/15] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 2 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index fc0a4b2fa7..049007a484 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3670,6 +3675,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 9ef7f6d768..0cfb0b3c5c 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Mon__2_Aug_2021_15_28_34_+0900_wlHCjIpnD/FrGAKu Content-Type: text/x-diff; name="v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v23-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v24 04/15] Allow to prolong life span of transition tables until transaction end @ 2019-12-20 01:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Yugo Nagata @ 2019-12-20 01:09 UTC (permalink / raw) Originally, tuplestores of AFTER trigger's transition tables were freed for each query depth. For our IVM implementation, we would like to prolong life of the tuplestores because we have to preserve them for a whole query assuming that some base tables might be changed in some trigger functions. --- src/backend/commands/trigger.c | 80 +++++++++++++++++++++++++++++++++- src/include/commands/trigger.h | 2 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d8890d2c74..c7a2bc3ed8 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3570,6 +3570,10 @@ typedef struct AfterTriggerEventList * end of the list, so it is relatively easy to discard them. The event * list chunks themselves are stored in event_cxt. * + * prolonged_tuplestored is a list of transition table tuplestores whose + * life are prolonged to the end of the outmost query instead of each nested + * query. + * * query_depth is the current depth of nested AfterTriggerBeginQuery calls * (-1 when the stack is empty). * @@ -3635,6 +3639,7 @@ typedef struct AfterTriggersData SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ MemoryContext event_cxt; /* memory context for events, if any */ + List *prolonged_tuplestores; /* list of prolonged tuplestores */ /* per-query-level data: */ AfterTriggersQueryData *query_stack; /* array of structs shown below */ @@ -3670,6 +3675,7 @@ struct AfterTriggersTableData bool closed; /* true when no longer OK to add tuples */ bool before_trig_done; /* did we already queue BS triggers? */ bool after_trig_done; /* did we already queue AS triggers? */ + bool prolonged; /* are transition tables prolonged? */ AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ @@ -4448,6 +4454,45 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, } +/* + * SetTransitionTablePreserved + * + * Prolong lifespan of transition tables corresponding specified relid and + * command type to the end of the outmost query instead of each nested query. + * This enables to use nested AFTER trigger's transition tables from outer + * query's triggers. Currently, only immediate incremental view maintenance + * uses this. + */ +void +SetTransitionTablePreserved(Oid relid, CmdType cmdType) +{ + AfterTriggersTableData *table; + AfterTriggersQueryData *qs; + bool found = false; + ListCell *lc; + + /* Check state, like AfterTriggerSaveEvent. */ + if (afterTriggers.query_depth < 0) + elog(ERROR, "SetTransitionTablePreserved() called outside of query"); + + qs = &afterTriggers.query_stack[afterTriggers.query_depth]; + + foreach(lc, qs->tables) + { + table = (AfterTriggersTableData *) lfirst(lc); + if (table->relid == relid && table->cmdType == cmdType && + table->closed) + { + table->prolonged = true; + found = true; + } + } + + if (!found) + elog(ERROR,"could not find table with OID %d and command type %d", relid, cmdType); +} + + /* * GetAfterTriggersTableData * @@ -4642,6 +4687,7 @@ AfterTriggerBeginXact(void) */ afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */ afterTriggers.query_depth = -1; + afterTriggers.prolonged_tuplestores = NIL; /* * Verify that there is no leftover state remaining. If these assertions @@ -4802,11 +4848,29 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) ts = table->old_tuplestore; table->old_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } ts = table->new_tuplestore; table->new_tuplestore = NULL; if (ts) - tuplestore_end(ts); + { + if (table->prolonged && afterTriggers.query_depth > 0) + { + MemoryContext oldcxt = MemoryContextSwitchTo(CurTransactionContext); + afterTriggers.prolonged_tuplestores = lappend(afterTriggers.prolonged_tuplestores, ts); + MemoryContextSwitchTo(oldcxt); + } + else + tuplestore_end(ts); + } if (table->storeslot) ExecDropSingleTupleTableSlot(table->storeslot); } @@ -4818,6 +4882,18 @@ AfterTriggerFreeQuery(AfterTriggersQueryData *qs) */ qs->tables = NIL; list_free_deep(tables); + + /* Release prolonged tuplestores at the end of the outmost query */ + if (afterTriggers.query_depth == 0) + { + foreach(lc, afterTriggers.prolonged_tuplestores) + { + ts = (Tuplestorestate *) lfirst(lc); + if (ts) + tuplestore_end(ts); + } + afterTriggers.prolonged_tuplestores = NIL; + } } diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 9ef7f6d768..0cfb0b3c5c 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -251,6 +251,8 @@ extern void AfterTriggerEndSubXact(bool isCommit); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt); extern bool AfterTriggerPendingOnRel(Oid relid); +extern void SetTransitionTablePreserved(Oid relid, CmdType cmdType); + /* * in utils/adt/ri_triggers.c -- 2.17.1 --Multipart=_Thu__23_Sep_2021_04_57_30_+0900_b5pmgR1N8oMaMz.U Content-Type: text/x-diff; name="v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Disposition: attachment; filename="v24-0005-Add-Incremental-View-Maintenance-support-to-pg_d.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 672+ messages in thread
* [PATCH v51 05/10] invert meaning of index_create flag bit @ 2026-04-03 19:08 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 672+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:08 UTC (permalink / raw) --- src/backend/catalog/index.c | 20 +++++++++++--------- src/backend/catalog/toasting.c | 2 +- src/backend/commands/indexcmds.c | 1 - src/include/catalog/index.h | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 4b8ed2c7660..5a7c9d81917 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -715,9 +715,9 @@ UpdateIndexRelation(Oid indexoid, * already exists. * INDEX_CREATE_PARTITIONED: * create a partitioned index (table must be partitioned) - * INDEX_CREATE_REPORT_PROGRESS: - * update the backend's progress information during index build. - + * INDEX_CREATE_SUPPRESS_PROGRESS: + * don't report progress during the index build. + * * constr_flags: flags passed to index_constraint_create * (only if INDEX_CREATE_ADD_CONSTRAINT is set) * allow_system_table_mods: allow table to be a system catalog @@ -763,7 +763,7 @@ index_create(Relation heapRelation, bool invalid = (flags & INDEX_CREATE_INVALID) != 0; bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0; bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0; - bool progress = (flags & INDEX_CREATE_REPORT_PROGRESS) != 0; + bool progress = (flags & INDEX_CREATE_SUPPRESS_PROGRESS) == 0; char relkind; TransactionId relfrozenxid; MultiXactId relminmxid; @@ -1454,13 +1454,15 @@ index_create_copy(Relation heapRelation, bool concurrently, } /* - * Note: The current callers do not need INDEX_CREATE_REPORT_PROGRESS. If - * 'concurrently' is true, there is no build at all. Otherwise the index - * build is a sub-command of REPACK. The current infrastructure does not - * allow two commands to report their progress at the same time. + * The current callers do not need to report progress: if 'concurrently' is + * true, there is no build at all to report about; and otherwise the index + * build is a sub-command of REPACK, and the current progress reporting + * infrastructure does not allow two commands to report their progress at + * the same time. */ if (concurrently) - flags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT; + flags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT | + INDEX_CREATE_SUPPRESS_PROGRESS; /* * Now create the new index. diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 51b27a8c71c..dcee536fd3f 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -332,7 +332,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, BTREE_AM_OID, rel->rd_rel->reltablespace, collationIds, opclassIds, NULL, coloptions, NULL, (Datum) 0, - INDEX_CREATE_IS_PRIMARY | INDEX_CREATE_REPORT_PROGRESS, 0, + INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL); table_close(toast_rel, NoLock); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 932924c13e0..cba379810c7 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -1231,7 +1231,6 @@ DefineIndex(ParseState *pstate, flags |= INDEX_CREATE_PARTITIONED; if (stmt->primary) flags |= INDEX_CREATE_IS_PRIMARY; - flags |= INDEX_CREATE_REPORT_PROGRESS; /* * If the table is partitioned, and recursion was declined but partitions diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 7ebe4f0bd87..9f538dac798 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -71,7 +71,7 @@ extern void index_check_primary_key(Relation heapRel, #define INDEX_CREATE_IF_NOT_EXISTS (1 << 4) #define INDEX_CREATE_PARTITIONED (1 << 5) #define INDEX_CREATE_INVALID (1 << 6) -#define INDEX_CREATE_REPORT_PROGRESS (1 << 7) +#define INDEX_CREATE_SUPPRESS_PROGRESS (1 << 7) extern Oid index_create(Relation heapRelation, const char *indexRelationName, -- 2.47.3 --r2slln3zpmilwu22 Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v51-0006-Error-out-any-process-that-would-block-at-REPACK.patch" ^ permalink raw reply [nested|flat] 672+ messages in thread
end of thread, other threads:[~2026-04-03 19:08 UTC | newest] Thread overview: 672+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v24 04/15] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v29 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v24 04/15] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v33 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v30 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v25 04/15] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v31 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v29 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v27 3/9] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v27 3/9] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v28 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v34 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v23 04/15] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v32 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v26 04/10] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v37 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v38 03/11] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2019-12-20 01:09 [PATCH v39 3/8] Allow to prolong life span of transition tables until transaction end Yugo Nagata <[email protected]> 2026-04-03 19:08 [PATCH v51 05/10] invert meaning of index_create flag bit Álvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox