From a9cd42d21f5355124288a584833f72cf1ce856cf Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Tue, 17 Jun 2025 21:38:40 -0700
Subject: [PATCH v58 03/10] Document temporal update/delete

The FOR PORTION OF syntax will also be documented in the reference pages
for UPDATE and DELETE, but this commit adds a new section to the DML chapter,
called "Updating and Deleting Temporal Data," giving a conceptual description,
as well as a glossary term for "temporal leftovers". The SQL standard doesn't
give any term for the supplementary INSERTs after an UPDATE/DELETE FOR PORTION
OF, but it is really handy to have a name for them.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
---
 doc/src/sgml/dml.sgml                   | 140 ++++++++++++++++++++++++
 doc/src/sgml/glossary.sgml              |  15 +++
 doc/src/sgml/images/Makefile            |   4 +-
 doc/src/sgml/images/temporal-delete.svg |  41 +++++++
 doc/src/sgml/images/temporal-delete.txt |  12 ++
 doc/src/sgml/images/temporal-update.svg |  45 ++++++++
 doc/src/sgml/images/temporal-update.txt |  12 ++
 7 files changed, 268 insertions(+), 1 deletion(-)
 create mode 100644 doc/src/sgml/images/temporal-delete.svg
 create mode 100644 doc/src/sgml/images/temporal-delete.txt
 create mode 100644 doc/src/sgml/images/temporal-update.svg
 create mode 100644 doc/src/sgml/images/temporal-update.txt

diff --git a/doc/src/sgml/dml.sgml b/doc/src/sgml/dml.sgml
index 458aee788b7..a899c9ab447 100644
--- a/doc/src/sgml/dml.sgml
+++ b/doc/src/sgml/dml.sgml
@@ -261,6 +261,146 @@ DELETE FROM products;
   </para>
  </sect1>
 
+ <sect1 id="dml-application-time-update-delete">
+  <title>Updating and Deleting Temporal Data</title>
+
+  <para>
+   Special syntax is available to update and delete from <link
+   linkend="ddl-application-time">application-time temporal tables</link>. (No
+   extra syntax is required to insert into them: the user just
+   provides the application time like any other attribute.) When updating
+   or deleting, the user can target a specific portion of history. Only
+   rows overlapping that history are affected, and within those rows only
+   the targeted history is changed. If a row contains more history beyond
+   what is targeted, its application time is reduced to fit within the
+   targeted interval, and new rows are inserted to preserve the history
+   that was not targeted.
+  </para>
+
+  <para>
+   Recall the example table from <xref linkend="temporal-entities-figure" />,
+   containing this data:
+
+<programlisting>
+ id | price |        valid_at
+----+-------+-------------------------
+  5 |     5 | [2020-01-01,2022-01-01)
+  5 |     8 | [2022-01-01,)
+  6 |     9 | [2021-01-01,2024-01-01)
+</programlisting>
+
+   A temporal update might look like this:
+
+<programlisting>
+UPDATE products
+  FOR PORTION OF valid_at FROM '2023-09-01' TO '2025-03-01'
+  AS p
+  SET price = 12
+  WHERE id = 5;
+</programlisting>
+
+   That command will update the second record for product 5. It will set the
+   price to 12 and
+   the application time to <literal>[2023-09-01,2025-03-01)</literal>.
+   Then, since the row's application time was originally
+   <literal>[2022-01-01,)</literal>, the command must insert two
+   <glossterm linkend="glossary-temporal-leftovers">temporal
+   leftovers</glossterm>: one for history before September 1, 2023, and
+   another for history since March 1, 2025. After the update, the table
+   has four rows for product 5:
+
+<programlisting>
+ id | price |        valid_at
+----+-------+-------------------------
+  5 |     5 | [2020-01-01,2022-01-01)
+  5 |     8 | [2022-01-01,2023-09-01)
+  5 |    12 | [2023-09-01,2025-03-01)
+  5 |     8 | [2025-03-01,)
+</programlisting>
+
+   The new history could be plotted as in <xref linkend="temporal-update-figure"/>.
+  </para>
+
+  <figure id="temporal-update-figure">
+   <title>Temporal Update Example</title>
+   <mediaobject>
+    <imageobject>
+     <imagedata fileref="images/temporal-update.svg" format="SVG" width="100%"/>
+    </imageobject>
+   </mediaobject>
+  </figure>
+
+  <para>
+   Similarly, a specific portion of history may be targeted when
+   deleting rows from a table. In that case, the original rows are
+   removed, but new
+   <glossterm linkend="glossary-temporal-leftovers">temporal leftovers</glossterm>
+   are inserted to preserve the untouched history. The syntax for a
+   temporal delete is:
+
+<programlisting>
+DELETE FROM products
+  FOR PORTION OF valid_at FROM '2021-08-01' TO '2023-09-01'
+  AS p
+WHERE id = 5;
+</programlisting>
+
+   Continuing the example, this command would delete two records. The
+   first record would yield a single temporal leftover, and the second
+   would be deleted entirely. The rows for product 5 would now be:
+
+<programlisting>
+ id | price |        valid_at
+----+-------+-------------------------
+  5 |     5 | [2020-01-01,2021-08-01)
+  5 |    12 | [2023-09-01,2025-03-01)
+  5 |     8 | [2025-03-01,)
+</programlisting>
+
+   The new history could be plotted as in <xref linkend="temporal-delete-figure"/>.
+  </para>
+
+  <figure id="temporal-delete-figure">
+   <title>Temporal Delete Example</title>
+   <mediaobject>
+    <imageobject>
+     <imagedata fileref="images/temporal-delete.svg" format="SVG" width="100%"/>
+    </imageobject>
+   </mediaobject>
+  </figure>
+
+  <para>
+   Instead of using the <literal>FROM ... TO ...</literal> syntax,
+   temporal update/delete commands can also give the targeted
+   range/multirange directly, inside parentheses. For example:
+   <literal>DELETE FROM products FOR PORTION OF valid_at ('[2028-01-01,)') ...</literal>.
+   This syntax is required when application time is stored
+   in a multirange column.
+  </para>
+
+  <para>
+   When application time is stored in a rangetype column, zero, one or
+   two temporal leftovers are produced by each row that is
+   updated/deleted. With a multirange column, only zero or one temporal
+   leftover is produced. The leftover bounds are computed using
+   <literal>range_minus_multi</literal> and
+   <literal>multirange_minus_multi</literal>
+   (see <xref linkend="functions-range"/>).
+  </para>
+
+  <para>
+   The bounds given to <literal>FOR PORTION OF</literal> must be
+   constant. Functions like <literal>NOW()</literal> are allowed, but
+   column references are not.
+  </para>
+
+  <para>
+   When temporal leftovers are inserted, all <literal>INSERT</literal>
+   triggers are fired, but permission checks for inserting rows are
+   skipped.
+  </para>
+ </sect1>
+
  <sect1 id="dml-returning">
   <title>Returning Data from Modified Rows</title>
 
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index a76cf5c383f..1f74dca5897 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -1916,6 +1916,21 @@
    </glossdef>
   </glossentry>
 
+  <glossentry id="glossary-temporal-leftovers">
+   <glossterm>Temporal leftovers</glossterm>
+   <glossdef>
+    <para>
+     After a temporal update or delete, the portion of history that was not
+     updated/deleted. When using ranges to track application time, there may be
+     zero, one, or two stretches of history that where not updated/deleted
+     (before and/or after the portion that was updated/deleted). New rows are
+     automatically inserted into the table to preserve that history. A single
+     multirange can accommodate the untouched history before and after the
+     update/delete, so there will be only zero or one leftover.
+    </para>
+   </glossdef>
+  </glossentry>
+
   <glossentry id="glossary-temporal-table">
    <glossterm>Temporal table</glossterm>
    <glossdef>
diff --git a/doc/src/sgml/images/Makefile b/doc/src/sgml/images/Makefile
index fd55b9ad23f..38f8869d78d 100644
--- a/doc/src/sgml/images/Makefile
+++ b/doc/src/sgml/images/Makefile
@@ -7,7 +7,9 @@ ALL_IMAGES = \
 	gin.svg \
 	pagelayout.svg \
 	temporal-entities.svg \
-	temporal-references.svg
+	temporal-references.svg \
+	temporal-update.svg \
+	temporal-delete.svg
 
 DITAA = ditaa
 DOT = dot
diff --git a/doc/src/sgml/images/temporal-delete.svg b/doc/src/sgml/images/temporal-delete.svg
new file mode 100644
index 00000000000..2d8b1d6ec7b
--- /dev/null
+++ b/doc/src/sgml/images/temporal-delete.svg
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1330 224" width="1330" height="224" shape-rendering="geometricPrecision" version="1.0">
+  <defs>
+    <filter id="f2" x="0" y="0" width="200%" height="200%">
+      <feOffset result="offOut" in="SourceGraphic" dx="5" dy="5"/>
+      <feGaussianBlur result="blurOut" in="offOut" stdDeviation="3"/>
+      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
+    </filter>
+  </defs>
+  <g stroke-width="1" stroke-linecap="square" stroke-linejoin="round">
+    <rect x="0" y="0" width="1330" height="224" style="fill: #ffffff"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M685.0 63.0 L685.0 147.0 L1005.0 147.0 L1005.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M315.0 63.0 L315.0 147.0 L25.0 147.0 L25.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M1005.0 63.0 L1005.0 147.0 L1275.0 147.0 L1275.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M205.0 168.0 L205.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M25.0 168.0 L25.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M385.0 168.0 L385.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M565.0 168.0 L565.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M745.0 168.0 L745.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M925.0 168.0 L925.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1105.0 168.0 L1105.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1285.0 168.0 L1285.0 181.0 "/>
+    <text x="46" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="40" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 5.00,</text>
+    <text x="83" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2020,1 Aug 2021))</text>
+    <text x="20" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2020</text>
+    <text x="200" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2021</text>
+    <text x="380" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2022</text>
+    <text x="560" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2023</text>
+    <text x="706" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="700" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 12.00,</text>
+    <text x="743" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Sep 2023,1 Mar 2025))</text>
+    <text x="740" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2024</text>
+    <text x="920" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2025</text>
+    <text x="1026" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="1020" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+    <text x="1056" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Mar 2025,))</text>
+    <text x="1100" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2026</text>
+    <text x="1289" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">...</text>
+  </g>
+</svg>
diff --git a/doc/src/sgml/images/temporal-delete.txt b/doc/src/sgml/images/temporal-delete.txt
new file mode 100644
index 00000000000..611df521569
--- /dev/null
+++ b/doc/src/sgml/images/temporal-delete.txt
@@ -0,0 +1,12 @@
+
+
++----------------------------+                                    +-------------------------------+--------------------------+
+| cGRE                       |                                    | cGRE                          | cGRE                     |
+| products                   |                                    | products                      | products                 |
+| (5, 5.00,                  |                                    | (5, 12.00,                    | (5, 8.00,                |
+|   [1 Jan 2020,1 Aug 2021)) |                                    |   [1 Sep 2023,1 Mar 2025))    |   [1 Mar 2025,))         |
+|                            |                                    |                               |                          |
++----------------------------+                                    +-------------------------------+--------------------------+
+
+|                 |                 |                 |                 |                 |                 |                 |
+2020              2021              2022              2023              2024              2025              2026              ...
diff --git a/doc/src/sgml/images/temporal-update.svg b/doc/src/sgml/images/temporal-update.svg
new file mode 100644
index 00000000000..6c7c43c8d22
--- /dev/null
+++ b/doc/src/sgml/images/temporal-update.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1330 224" width="1330" height="224" shape-rendering="geometricPrecision" version="1.0">
+  <defs>
+    <filter id="f2" x="0" y="0" width="200%" height="200%">
+      <feOffset result="offOut" in="SourceGraphic" dx="5" dy="5"/>
+      <feGaussianBlur result="blurOut" in="offOut" stdDeviation="3"/>
+      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
+    </filter>
+  </defs>
+  <g stroke-width="1" stroke-linecap="square" stroke-linejoin="round">
+    <rect x="0" y="0" width="1330" height="224" style="fill: #ffffff"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M385.0 63.0 L385.0 147.0 L25.0 147.0 L25.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M1285.0 63.0 L1285.0 147.0 L975.0 147.0 L975.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M385.0 147.0 L685.0 147.0 L685.0 63.0 L385.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M685.0 63.0 L685.0 147.0 L975.0 147.0 L975.0 63.0 z"/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M205.0 168.0 L205.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M25.0 168.0 L25.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M385.0 168.0 L385.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M565.0 168.0 L565.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M745.0 168.0 L745.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M925.0 168.0 L925.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1105.0 168.0 L1105.0 181.0 "/>
+    <path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M1285.0 168.0 L1285.0 181.0 "/>
+    <text x="46" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="40" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 5.00,</text>
+    <text x="86" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2020,1 Jan 2022))</text>
+    <text x="20" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2020</text>
+    <text x="200" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2021</text>
+    <text x="406" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="400" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+    <text x="445" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Jan 2022,1 Sep 2023))</text>
+    <text x="380" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2022</text>
+    <text x="560" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2023</text>
+    <text x="706" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="700" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 12.00,</text>
+    <text x="743" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Sep 2023,1 Mar 2025))</text>
+    <text x="740" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2024</text>
+    <text x="920" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2025</text>
+    <text x="996" y="96" font-family="Courier" font-size="15" stroke="none" fill="#000000">products</text>
+    <text x="990" y="110" font-family="Courier" font-size="15" stroke="none" fill="#000000">(5, 8.00,</text>
+    <text x="1026" y="124" font-family="Courier" font-size="15" stroke="none" fill="#000000">[1 Mar 2025,))</text>
+    <text x="1100" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">2026</text>
+    <text x="1289" y="194" font-family="Courier" font-size="15" stroke="none" fill="#000000">...</text>
+  </g>
+</svg>
diff --git a/doc/src/sgml/images/temporal-update.txt b/doc/src/sgml/images/temporal-update.txt
new file mode 100644
index 00000000000..7e862d89437
--- /dev/null
+++ b/doc/src/sgml/images/temporal-update.txt
@@ -0,0 +1,12 @@
+
+
++-----------------------------------+-----------------------------+----------------------------+------------------------------+
+| cGRE                              | cGRE                        | cGRE                       | cGRE                         |
+| products                          | products                    | products                   | products                     |
+| (5, 5.00,                         | (5, 8.00,                   | (5, 12.00,                 | (5, 8.00,                    |
+|   [1 Jan 2020,1 Jan 2022))        |   [1 Jan 2022,1 Sep 2023))  |   [1 Sep 2023,1 Mar 2025)) |   [1 Mar 2025,))             |
+|                                   |                             |                            |                              |
++-----------------------------------+-----------------------------+----------------------------+------------------------------+
+
+|                 |                 |                 |                 |                 |                 |                 |
+2020              2021              2022              2023              2024              2025              2026              ...
-- 
2.45.0

