From ffd3194276ee6f928926e6528862c777ed9bce15 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Tue, 29 Oct 2024 18:54:37 -0700
Subject: [PATCH v1 2/5] Expose FOR PORTION OF to plpgsql triggers

It is helpful for triggers to see what the FOR PORTION OF clause
specified: both the column name and the targeted bounds. Our RI
triggers require this information, and we are passing it as part of the
TriggerData struct. This commit allows plpgsql trigger functions to
access the same information, using the new TG_PERIOD_NAME and
TG_PERIOD_BOUNDS variables.

These variables are also set when inserting temporal leftovers, so that triggers
can distinguish regular inserts from those automatic ones. Since an INSERT can't
use FOR PORTION OF itself, there is no ambiguity.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
---
 .../expected/level_tracking.out               |   2 +-
 doc/src/sgml/plpgsql.sgml                     |  29 +++
 src/pl/plpgsql/src/pl_comp.c                  |  26 +++
 src/pl/plpgsql/src/pl_exec.c                  |  32 +++
 src/pl/plpgsql/src/plpgsql.h                  |   2 +
 src/test/regress/expected/for_portion_of.out  | 193 ++++++++++++------
 src/test/regress/sql/for_portion_of.sql       |  47 ++++-
 7 files changed, 264 insertions(+), 67 deletions(-)

diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out
index 832d65e97ca..d664201498d 100644
--- a/contrib/pg_stat_statements/expected/level_tracking.out
+++ b/contrib/pg_stat_statements/expected/level_tracking.out
@@ -1598,7 +1598,7 @@ SELECT toplevel, calls, rows, plans, query FROM pg_stat_statements
   ORDER BY query COLLATE "C";
  toplevel | calls | rows | plans |                        query                        
 ----------+-------+------+-------+-----------------------------------------------------
- f        |     2 |    2 |     0 | INSERT INTO audit_table VALUES ($15, TG_OP, NEW.id)
+ f        |     2 |    2 |     0 | INSERT INTO audit_table VALUES ($17, TG_OP, NEW.id)
  t        |     2 |    2 |     0 | INSERT INTO test_trigger VALUES ($1, $2)
  t        |     1 |    1 |     0 | SELECT pg_stat_statements_reset() IS NOT NULL AS t
 (3 rows)
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 561f6e50d63..2750a2a40a2 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4247,6 +4247,35 @@ ASSERT <replaceable class="parameter">condition</replaceable> <optional> , <repl
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry id="plpgsql-dml-trigger-tg-temporal-column">
+     <term><varname>TG_PERIOD_NAME</varname> <type>text</type></term>
+     <listitem>
+      <para>
+       the column name used in a <literal>FOR PORTION OF</literal> clause,
+       or else <symbol>NULL</symbol>. Also set for the implicit
+       <literal>INSERT</literal> statements to add the
+       <glossterm linkend="glossary-temporal-leftovers">temporal
+       leftovers</glossterm>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry id="plpgsql-dml-trigger-tg-temporal-target">
+     <term><varname>TG_PERIOD_BOUNDS</varname> <type>text</type></term>
+     <listitem>
+      <para>
+       the range/multirange given as the bounds of a
+       <literal>FOR PORTION OF</literal> clause, either directly (with parens syntax)
+       or computed from the <literal>FROM</literal> and <literal>TO</literal> bounds.
+       <symbol>NULL</symbol> if <literal>FOR PORTION OF</literal> was not used.
+       Also set for the implicit <literal>INSERT</literal> statements to add the
+       <glossterm linkend="glossary-temporal-leftovers">temporal
+       leftovers</glossterm>. This is a text value based on the type's output
+       function, since the type can't be known at function creation time.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
   </para>
 
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 75184b75707..9550bdcc112 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -617,6 +617,32 @@ plpgsql_compile_callback(FunctionCallInfo fcinfo,
 			var->dtype = PLPGSQL_DTYPE_PROMISE;
 			((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
 
+			/* Add the variable tg_period_name */
+			var = plpgsql_build_variable("tg_period_name", 0,
+										 plpgsql_build_datatype(TEXTOID,
+																-1,
+																function->fn_input_collation,
+																NULL),
+										 true);
+			Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+			var->dtype = PLPGSQL_DTYPE_PROMISE;
+			((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_PERIOD_NAME;
+
+			/*
+			 * Add the variable tg_period_bounds. This could be any rangetype
+			 * or multirangetype or (XXX: someday) user-supplied type, so the
+			 * best we can offer is a TEXT variable.
+			 */
+			var = plpgsql_build_variable("tg_period_bounds", 0,
+										 plpgsql_build_datatype(TEXTOID,
+																-1,
+																function->fn_input_collation,
+																NULL),
+										 true);
+			Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+			var->dtype = PLPGSQL_DTYPE_PROMISE;
+			((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_PERIOD_BOUNDS;
+
 			break;
 
 		case PLPGSQL_EVENT_TRIGGER:
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 341beb496b8..68818477701 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -1416,6 +1416,7 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
 						PLpgSQL_var *var)
 {
 	MemoryContext oldcontext;
+	ForPortionOfState *fpo;
 
 	if (var->promise == PLPGSQL_PROMISE_NONE)
 		return;					/* nothing to do */
@@ -1547,6 +1548,37 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
 			}
 			break;
 
+		case PLPGSQL_PROMISE_TG_PERIOD_NAME:
+			if (estate->trigdata == NULL)
+				elog(ERROR, "trigger promise is not in a trigger function");
+			if (estate->trigdata->tg_temporal)
+				assign_text_var(estate, var, estate->trigdata->tg_temporal->fp_rangeName);
+			else
+				assign_simple_var(estate, var, (Datum) 0, true, false);
+			break;
+
+		case PLPGSQL_PROMISE_TG_PERIOD_BOUNDS:
+			if (estate->trigdata == NULL)
+				elog(ERROR, "trigger promise is not in a trigger function");
+
+			fpo = estate->trigdata->tg_temporal;
+			if (fpo)
+			{
+
+				Oid			funcid;
+				bool		varlena;
+
+				getTypeOutputInfo(fpo->fp_rangeType, &funcid, &varlena);
+				Assert(OidIsValid(funcid));
+
+				assign_text_var(estate, var,
+								OidOutputFunctionCall(funcid,
+													  fpo->fp_targetRange));
+			}
+			else
+				assign_simple_var(estate, var, (Datum) 0, true, false);
+			break;
+
 		case PLPGSQL_PROMISE_TG_EVENT:
 			if (estate->evtrigdata == NULL)
 				elog(ERROR, "event trigger promise is not in an event trigger function");
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index addb14a9959..70ffbb3b29a 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -85,6 +85,8 @@ typedef enum PLpgSQL_promise_type
 	PLPGSQL_PROMISE_TG_ARGV,
 	PLPGSQL_PROMISE_TG_EVENT,
 	PLPGSQL_PROMISE_TG_TAG,
+	PLPGSQL_PROMISE_TG_PERIOD_NAME,
+	PLPGSQL_PROMISE_TG_PERIOD_BOUNDS,
 } PLpgSQL_promise_type;
 
 /*
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
index 1a53e549de0..08ffd806598 100644
--- a/src/test/regress/expected/for_portion_of.out
+++ b/src/test/regress/expected/for_portion_of.out
@@ -1350,8 +1350,13 @@ CREATE FUNCTION dump_trigger()
 RETURNS TRIGGER LANGUAGE plpgsql AS
 $$
 BEGIN
-  RAISE NOTICE '%: % % %:',
-    TG_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+  IF TG_PERIOD_NAME IS NOT NULL THEN
+    RAISE NOTICE '%: % % FOR PORTION OF % (%) %:',
+      TG_NAME, TG_WHEN, TG_OP, TG_PERIOD_NAME, TG_PERIOD_BOUNDS, TG_LEVEL;
+  ELSE
+    RAISE NOTICE '%: % % %:',
+      TG_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+  END IF;
 
   IF TG_ARGV[0] THEN
     RAISE NOTICE '  old: %', (SELECT string_agg(old_table::text, '\n       ') FROM old_table);
@@ -1407,85 +1412,85 @@ UPDATE for_portion_of_test
   FOR PORTION OF valid_at FROM '2021-01-01' TO '2022-01-01'
   SET name = 'five^3'
   WHERE id = '[5,6)';
-NOTICE:  fpo_before_stmt: BEFORE UPDATE STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_stmt1: BEFORE UPDATE STATEMENT:
+NOTICE:  fpo_before_stmt1: BEFORE UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE UPDATE ROW:
+NOTICE:  fpo_before_row: BEFORE UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: [2019-01-01,2030-01-01)
 NOTICE:    new: [2021-01-01,2022-01-01)
-NOTICE:  fpo_before_row1: BEFORE UPDATE ROW:
+NOTICE:  fpo_before_row1: BEFORE UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: [2019-01-01,2030-01-01)
 NOTICE:    new: [2021-01-01,2022-01-01)
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2019-01-01,2021-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2019-01-01,2021-01-01)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2022-01-01,2030-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2022-01-01,2030-01-01)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_after_update_row: AFTER UPDATE ROW:
+NOTICE:  fpo_after_update_row: AFTER UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) ROW:
 NOTICE:    old: [2019-01-01,2030-01-01)
 NOTICE:    new: [2021-01-01,2022-01-01)
-NOTICE:  fpo_after_update_stmt: AFTER UPDATE STATEMENT:
+NOTICE:  fpo_after_update_stmt: AFTER UPDATE FOR PORTION OF valid_at ([2021-01-01,2022-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
 DELETE FROM for_portion_of_test
   FOR PORTION OF valid_at FROM '2023-01-01' TO '2024-01-01'
   WHERE id = '[5,6)';
-NOTICE:  fpo_before_stmt: BEFORE DELETE STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE DELETE FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE DELETE ROW:
+NOTICE:  fpo_before_row: BEFORE DELETE FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: [2022-01-01,2030-01-01)
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2022-01-01,2023-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2022-01-01,2023-01-01)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2024-01-01,2030-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2024-01-01,2030-01-01)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_after_delete_row: AFTER DELETE ROW:
+NOTICE:  fpo_after_delete_row: AFTER DELETE FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) ROW:
 NOTICE:    old: [2022-01-01,2030-01-01)
 NOTICE:    new: <NULL>
-NOTICE:  fpo_after_delete_stmt: AFTER DELETE STATEMENT:
+NOTICE:  fpo_after_delete_stmt: AFTER DELETE FOR PORTION OF valid_at ([2023-01-01,2024-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
 SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
@@ -1553,68 +1558,68 @@ BEGIN;
 UPDATE for_portion_of_test
   FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
   SET name = '2018-01-15_to_2019-01-01';
-NOTICE:  fpo_before_stmt: BEFORE UPDATE STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE UPDATE FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE UPDATE ROW:
+NOTICE:  fpo_before_row: BEFORE UPDATE FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: [2018-01-01,2020-01-01)
 NOTICE:    new: [2018-01-15,2019-01-01)
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2018-01-01,2018-01-15)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-01,2018-01-15)",one)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-01,2018-01-15)",one)
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2019-01-01,2020-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2019-01-01,2020-01-01)",one)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2019-01-01,2020-01-01)",one)
-NOTICE:  fpo_after_update_row: AFTER UPDATE ROW:
+NOTICE:  fpo_after_update_row: AFTER UPDATE FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: ("[1,2)","[2018-01-15,2019-01-01)",2018-01-15_to_2019-01-01)
-NOTICE:  fpo_after_update_stmt: AFTER UPDATE STATEMENT:
+NOTICE:  fpo_after_update_stmt: AFTER UPDATE FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) STATEMENT:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: ("[1,2)","[2018-01-15,2019-01-01)",2018-01-15_to_2019-01-01)
 ROLLBACK;
 BEGIN;
 DELETE FROM for_portion_of_test
   FOR PORTION OF valid_at FROM NULL TO '2018-01-21';
-NOTICE:  fpo_before_stmt: BEFORE DELETE STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE DELETE FOR PORTION OF valid_at ((,2018-01-21)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE DELETE ROW:
+NOTICE:  fpo_before_row: BEFORE DELETE FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: [2018-01-01,2020-01-01)
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ((,2018-01-21)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2018-01-21,2020-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-21,2020-01-01)",one)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ((,2018-01-21)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-21,2020-01-01)",one)
-NOTICE:  fpo_after_delete_row: AFTER DELETE ROW:
+NOTICE:  fpo_after_delete_row: AFTER DELETE FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: <NULL>
-NOTICE:  fpo_after_delete_stmt: AFTER DELETE STATEMENT:
+NOTICE:  fpo_after_delete_stmt: AFTER DELETE FOR PORTION OF valid_at ((,2018-01-21)) STATEMENT:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: <NULL>
 ROLLBACK;
@@ -1622,28 +1627,28 @@ BEGIN;
 UPDATE for_portion_of_test
   FOR PORTION OF valid_at FROM NULL TO '2018-01-02'
   SET name = 'NULL_to_2018-01-01';
-NOTICE:  fpo_before_stmt: BEFORE UPDATE STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE UPDATE FOR PORTION OF valid_at ((,2018-01-02)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE UPDATE ROW:
+NOTICE:  fpo_before_row: BEFORE UPDATE FOR PORTION OF valid_at ((,2018-01-02)) ROW:
 NOTICE:    old: [2018-01-01,2020-01-01)
 NOTICE:    new: [2018-01-01,2018-01-02)
-NOTICE:  fpo_before_stmt: BEFORE INSERT STATEMENT:
+NOTICE:  fpo_before_stmt: BEFORE INSERT FOR PORTION OF valid_at ((,2018-01-02)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row: BEFORE INSERT ROW:
+NOTICE:  fpo_before_row: BEFORE INSERT FOR PORTION OF valid_at ((,2018-01-02)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2018-01-02,2020-01-01)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ((,2018-01-02)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-02,2020-01-01)",one)
-NOTICE:  fpo_after_insert_stmt: AFTER INSERT STATEMENT:
+NOTICE:  fpo_after_insert_stmt: AFTER INSERT FOR PORTION OF valid_at ((,2018-01-02)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: ("[1,2)","[2018-01-02,2020-01-01)",one)
-NOTICE:  fpo_after_update_row: AFTER UPDATE ROW:
+NOTICE:  fpo_after_update_row: AFTER UPDATE FOR PORTION OF valid_at ((,2018-01-02)) ROW:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: ("[1,2)","[2018-01-01,2018-01-02)",NULL_to_2018-01-01)
-NOTICE:  fpo_after_update_stmt: AFTER UPDATE STATEMENT:
+NOTICE:  fpo_after_update_stmt: AFTER UPDATE FOR PORTION OF valid_at ((,2018-01-02)) STATEMENT:
 NOTICE:    old: ("[1,2)","[2018-01-01,2020-01-01)",one)
 NOTICE:    new: ("[1,2)","[2018-01-01,2018-01-02)",NULL_to_2018-01-01)
 ROLLBACK;
@@ -1674,26 +1679,26 @@ UPDATE for_portion_of_test
   FOR PORTION OF valid_at FROM '2018-01-15' TO '2019-01-01'
   SET name = '2018-01-15_to_2019-01-01';
 COMMIT;
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2018-01-01,2018-01-15)
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2019-01-01,2020-01-01)
-NOTICE:  fpo_after_update_row: AFTER UPDATE ROW:
+NOTICE:  fpo_after_update_row: AFTER UPDATE FOR PORTION OF valid_at ([2018-01-15,2019-01-01)) ROW:
 NOTICE:    old: [2018-01-01,2020-01-01)
 NOTICE:    new: [2018-01-15,2019-01-01)
 BEGIN;
 DELETE FROM for_portion_of_test
   FOR PORTION OF valid_at FROM NULL TO '2018-01-21';
 COMMIT;
-NOTICE:  fpo_after_insert_row: AFTER INSERT ROW:
+NOTICE:  fpo_after_insert_row: AFTER INSERT FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: <NULL>
 NOTICE:    new: [2018-01-21,2019-01-01)
-NOTICE:  fpo_after_delete_row: AFTER DELETE ROW:
+NOTICE:  fpo_after_delete_row: AFTER DELETE FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: [2018-01-15,2019-01-01)
 NOTICE:    new: <NULL>
-NOTICE:  fpo_after_delete_row: AFTER DELETE ROW:
+NOTICE:  fpo_after_delete_row: AFTER DELETE FOR PORTION OF valid_at ((,2018-01-21)) ROW:
 NOTICE:    old: [2018-01-01,2018-01-15)
 NOTICE:    new: <NULL>
 BEGIN;
@@ -1708,6 +1713,66 @@ SELECT * FROM for_portion_of_test;
  [1,2) | [2018-01-21,2019-01-01) | 2018-01-15_to_2019-01-01
 (2 rows)
 
+-- test inserts fired from triggers during FOR PORTION OF:
+DROP TABLE for_portion_of_test;
+CREATE TABLE for_portion_of_test (
+  id int4range,
+  valid_at daterange,
+  name text
+);
+INSERT INTO for_portion_of_test (id, valid_at, name) VALUES
+  ('[1,2)', '[2018-01-01,2020-01-01)', 'one');
+CREATE FUNCTION trg_fpo_insert()
+RETURNS TRIGGER LANGUAGE plpgsql AS
+$$
+BEGIN
+  IF pg_trigger_depth() = 1 THEN
+    INSERT INTO for_portion_of_test
+      (id, valid_at, name) VALUES
+      (int4range(lower(NEW.id) + 1, upper(NEW.id) + 1), NEW.valid_at, NEW.name);
+  END IF;
+  RETURN CASE WHEN 'TG_OP' = 'DELETE' THEN OLD ELSE NEW END;
+END;
+$$;
+CREATE TRIGGER fpo_01_dump_before
+  BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test
+  FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false);
+CREATE TRIGGER fpo_02_before_row
+  BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test
+  FOR EACH ROW EXECUTE PROCEDURE trg_fpo_insert();
+UPDATE for_portion_of_test
+  FOR PORTION OF valid_at FROM '2019-01-01' TO '2019-06-01'
+  SET name = CONCAT(name, '^')
+  WHERE id = '[1,2)';
+NOTICE:  fpo_01_dump_before: BEFORE UPDATE FOR PORTION OF valid_at ([2019-01-01,2019-06-01)) ROW:
+NOTICE:    old: [2018-01-01,2020-01-01)
+NOTICE:    new: [2019-01-01,2019-06-01)
+NOTICE:  fpo_01_dump_before: BEFORE INSERT ROW:
+NOTICE:    old: <NULL>
+NOTICE:    new: [2019-01-01,2019-06-01)
+NOTICE:  fpo_01_dump_before: BEFORE INSERT FOR PORTION OF valid_at ([2019-01-01,2019-06-01)) ROW:
+NOTICE:    old: <NULL>
+NOTICE:    new: [2018-01-01,2019-01-01)
+NOTICE:  fpo_01_dump_before: BEFORE INSERT ROW:
+NOTICE:    old: <NULL>
+NOTICE:    new: [2018-01-01,2019-01-01)
+NOTICE:  fpo_01_dump_before: BEFORE INSERT FOR PORTION OF valid_at ([2019-01-01,2019-06-01)) ROW:
+NOTICE:    old: <NULL>
+NOTICE:    new: [2019-06-01,2020-01-01)
+NOTICE:  fpo_01_dump_before: BEFORE INSERT ROW:
+NOTICE:    old: <NULL>
+NOTICE:    new: [2019-06-01,2020-01-01)
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+  id   |        valid_at         | name 
+-------+-------------------------+------
+ [1,2) | [2018-01-01,2019-01-01) | one
+ [1,2) | [2019-01-01,2019-06-01) | one^
+ [1,2) | [2019-06-01,2020-01-01) | one
+ [2,3) | [2018-01-01,2019-01-01) | one
+ [2,3) | [2019-01-01,2019-06-01) | one^
+ [2,3) | [2019-06-01,2020-01-01) | one
+(6 rows)
+
 -- test FOR PORTION OF from triggers during FOR PORTION OF:
 DROP TABLE for_portion_of_test;
 CREATE TABLE for_portion_of_test (
@@ -2610,10 +2675,10 @@ CREATE TRIGGER fpo_before_row2
 UPDATE fpo_update_of_trigger
   FOR PORTION OF valid_at FROM 30 TO 70
   SET id = 2;
-NOTICE:  fpo_before_row2: BEFORE UPDATE STATEMENT:
+NOTICE:  fpo_before_row2: BEFORE UPDATE FOR PORTION OF valid_at ([30,70)) STATEMENT:
 NOTICE:    old: <NULL>
 NOTICE:    new: <NULL>
-NOTICE:  fpo_before_row1: BEFORE UPDATE ROW:
+NOTICE:  fpo_before_row1: BEFORE UPDATE FOR PORTION OF valid_at ([30,70)) ROW:
 NOTICE:    old: [10,100)
 NOTICE:    new: [30,70)
 DROP TABLE fpo_update_of_trigger;
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
index 955cf666d66..09e93309483 100644
--- a/src/test/regress/sql/for_portion_of.sql
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -893,8 +893,13 @@ CREATE FUNCTION dump_trigger()
 RETURNS TRIGGER LANGUAGE plpgsql AS
 $$
 BEGIN
-  RAISE NOTICE '%: % % %:',
-    TG_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+  IF TG_PERIOD_NAME IS NOT NULL THEN
+    RAISE NOTICE '%: % % FOR PORTION OF % (%) %:',
+      TG_NAME, TG_WHEN, TG_OP, TG_PERIOD_NAME, TG_PERIOD_BOUNDS, TG_LEVEL;
+  ELSE
+    RAISE NOTICE '%: % % %:',
+      TG_NAME, TG_WHEN, TG_OP, TG_LEVEL;
+  END IF;
 
   IF TG_ARGV[0] THEN
     RAISE NOTICE '  old: %', (SELECT string_agg(old_table::text, '\n       ') FROM old_table);
@@ -1086,6 +1091,44 @@ COMMIT;
 
 SELECT * FROM for_portion_of_test;
 
+-- test inserts fired from triggers during FOR PORTION OF:
+
+DROP TABLE for_portion_of_test;
+CREATE TABLE for_portion_of_test (
+  id int4range,
+  valid_at daterange,
+  name text
+);
+INSERT INTO for_portion_of_test (id, valid_at, name) VALUES
+  ('[1,2)', '[2018-01-01,2020-01-01)', 'one');
+
+CREATE FUNCTION trg_fpo_insert()
+RETURNS TRIGGER LANGUAGE plpgsql AS
+$$
+BEGIN
+  IF pg_trigger_depth() = 1 THEN
+    INSERT INTO for_portion_of_test
+      (id, valid_at, name) VALUES
+      (int4range(lower(NEW.id) + 1, upper(NEW.id) + 1), NEW.valid_at, NEW.name);
+  END IF;
+  RETURN CASE WHEN 'TG_OP' = 'DELETE' THEN OLD ELSE NEW END;
+END;
+$$;
+
+CREATE TRIGGER fpo_01_dump_before
+  BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test
+  FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false);
+CREATE TRIGGER fpo_02_before_row
+  BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test
+  FOR EACH ROW EXECUTE PROCEDURE trg_fpo_insert();
+
+UPDATE for_portion_of_test
+  FOR PORTION OF valid_at FROM '2019-01-01' TO '2019-06-01'
+  SET name = CONCAT(name, '^')
+  WHERE id = '[1,2)';
+
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+
 -- test FOR PORTION OF from triggers during FOR PORTION OF:
 
 DROP TABLE for_portion_of_test;
-- 
2.47.3

