Skip to content

Commit d415afc

Browse files
pjungwirCommitfest Bot
authored andcommitted
Add UPDATE/DELETE FOR PORTION OF
- Added bison support for FOR PORTION OF syntax. The bounds must be constant, so we forbid column references, subqueries, etc. We do accept functions like NOW(). - Added logic to executor to insert new rows for the "temporal leftover" part of a record touched by a FOR PORTION OF query. - Documented FOR PORTION OF. - Added tests. Author: Paul A. Jungwirth <[email protected]>
1 parent 4397d92 commit d415afc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3804
-89
lines changed

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,19 @@ CREATE TABLE "S 1"."T 4" (
5050
c3 text,
5151
CONSTRAINT t4_pkey PRIMARY KEY (c1)
5252
);
53+
CREATE TABLE "S 1"."T 5" (
54+
c1 int4range NOT NULL,
55+
c2 int NOT NULL,
56+
c3 text,
57+
c4 daterange NOT NULL,
58+
CONSTRAINT t5_pkey PRIMARY KEY (c1, c4 WITHOUT OVERLAPS)
59+
);
5360
-- Disable autovacuum for these tables to avoid unexpected effects of that
5461
ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false');
5562
ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false');
5663
ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false');
5764
ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false');
65+
ALTER TABLE "S 1"."T 5" SET (autovacuum_enabled = 'false');
5866
INSERT INTO "S 1"."T 1"
5967
SELECT id,
6068
id % 10,
@@ -81,10 +89,17 @@ INSERT INTO "S 1"."T 4"
8189
'AAA' || to_char(id, 'FM000')
8290
FROM generate_series(1, 100) id;
8391
DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests
92+
INSERT INTO "S 1"."T 5"
93+
SELECT int4range(id, id + 1),
94+
id + 1,
95+
'AAA' || to_char(id, 'FM000'),
96+
'[2000-01-01,2020-01-01)'
97+
FROM generate_series(1, 100) id;
8498
ANALYZE "S 1"."T 1";
8599
ANALYZE "S 1"."T 2";
86100
ANALYZE "S 1"."T 3";
87101
ANALYZE "S 1"."T 4";
102+
ANALYZE "S 1"."T 5";
88103
-- ===================================================================
89104
-- create foreign tables
90105
-- ===================================================================
@@ -132,6 +147,12 @@ CREATE FOREIGN TABLE ft7 (
132147
c2 int NOT NULL,
133148
c3 text
134149
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
150+
CREATE FOREIGN TABLE ft8 (
151+
c1 int4range NOT NULL,
152+
c2 int NOT NULL,
153+
c3 text,
154+
c4 daterange NOT NULL
155+
) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 5');
135156
-- ===================================================================
136157
-- tests for validator
137158
-- ===================================================================
@@ -214,7 +235,8 @@ ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
214235
public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') |
215236
public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') |
216237
public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') |
217-
(6 rows)
238+
public | ft8 | loopback | (schema_name 'S 1', table_name 'T 5') |
239+
(7 rows)
218240

219241
-- Test that alteration of server options causes reconnection
220242
-- Remote's errors might be non-English, so hide them to ensure stable results
@@ -6303,6 +6325,27 @@ DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
63036325
ft2
63046326
(1 row)
63056327

6328+
-- Test UPDATE FOR PORTION OF
6329+
UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
6330+
SET c2 = c2 + 1
6331+
WHERE c1 = '[1,2)';
6332+
ERROR: foreign tables don't support FOR PORTION OF
6333+
SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
6334+
c1 | c2 | c3 | c4
6335+
-------+----+--------+-------------------------
6336+
[1,2) | 2 | AAA001 | [01-01-2000,01-01-2020)
6337+
(1 row)
6338+
6339+
-- Test DELETE FOR PORTION OF
6340+
DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
6341+
WHERE c1 = '[2,3)';
6342+
ERROR: foreign tables don't support FOR PORTION OF
6343+
SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
6344+
c1 | c2 | c3 | c4
6345+
-------+----+--------+-------------------------
6346+
[2,3) | 3 | AAA002 | [01-01-2000,01-01-2020)
6347+
(1 row)
6348+
63066349
-- Test UPDATE/DELETE with RETURNING on a three-table join
63076350
INSERT INTO ft2 (c1,c2,c3)
63086351
SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id;

contrib/postgres_fdw/sql/postgres_fdw.sql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,20 @@ CREATE TABLE "S 1"."T 4" (
5454
c3 text,
5555
CONSTRAINT t4_pkey PRIMARY KEY (c1)
5656
);
57+
CREATE TABLE "S 1"."T 5" (
58+
c1 int4range NOT NULL,
59+
c2 int NOT NULL,
60+
c3 text,
61+
c4 daterange NOT NULL,
62+
CONSTRAINT t5_pkey PRIMARY KEY (c1, c4 WITHOUT OVERLAPS)
63+
);
5764

5865
-- Disable autovacuum for these tables to avoid unexpected effects of that
5966
ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false');
6067
ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false');
6168
ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false');
6269
ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false');
70+
ALTER TABLE "S 1"."T 5" SET (autovacuum_enabled = 'false');
6371

6472
INSERT INTO "S 1"."T 1"
6573
SELECT id,
@@ -87,11 +95,18 @@ INSERT INTO "S 1"."T 4"
8795
'AAA' || to_char(id, 'FM000')
8896
FROM generate_series(1, 100) id;
8997
DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests
98+
INSERT INTO "S 1"."T 5"
99+
SELECT int4range(id, id + 1),
100+
id + 1,
101+
'AAA' || to_char(id, 'FM000'),
102+
'[2000-01-01,2020-01-01)'
103+
FROM generate_series(1, 100) id;
90104

91105
ANALYZE "S 1"."T 1";
92106
ANALYZE "S 1"."T 2";
93107
ANALYZE "S 1"."T 3";
94108
ANALYZE "S 1"."T 4";
109+
ANALYZE "S 1"."T 5";
95110

96111
-- ===================================================================
97112
-- create foreign tables
@@ -146,6 +161,14 @@ CREATE FOREIGN TABLE ft7 (
146161
c3 text
147162
) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4');
148163

164+
CREATE FOREIGN TABLE ft8 (
165+
c1 int4range NOT NULL,
166+
c2 int NOT NULL,
167+
c3 text,
168+
c4 daterange NOT NULL
169+
) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 5');
170+
171+
149172
-- ===================================================================
150173
-- tests for validator
151174
-- ===================================================================
@@ -1538,6 +1561,17 @@ EXPLAIN (verbose, costs off)
15381561
DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down
15391562
DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass;
15401563

1564+
-- Test UPDATE FOR PORTION OF
1565+
UPDATE ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
1566+
SET c2 = c2 + 1
1567+
WHERE c1 = '[1,2)';
1568+
SELECT * FROM ft8 WHERE c1 = '[1,2)' ORDER BY c1, c4;
1569+
1570+
-- Test DELETE FOR PORTION OF
1571+
DELETE FROM ft8 FOR PORTION OF c4 FROM '2005-01-01' TO '2006-01-01'
1572+
WHERE c1 = '[2,3)';
1573+
SELECT * FROM ft8 WHERE c1 = '[2,3)' ORDER BY c1, c4;
1574+
15411575
-- Test UPDATE/DELETE with RETURNING on a three-table join
15421576
INSERT INTO ft2 (c1,c2,c3)
15431577
SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id;

doc/src/sgml/ref/create_publication.sgml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,12 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
390390
for each row inserted, updated, or deleted.
391391
</para>
392392

393+
<para>
394+
For a <command>FOR PORTION OF</command> command, the publication will publish an
395+
<command>UPDATE</command> or <command>DELETE</command>, followed by one
396+
<command>INSERT</command> for each temporal leftover row inserted.
397+
</para>
398+
393399
<para>
394400
<command>ATTACH</command>ing a table into a partition tree whose root is
395401
published using a publication with <literal>publish_via_partition_root</literal>

doc/src/sgml/ref/delete.sgml

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ PostgreSQL documentation
2222
<refsynopsisdiv>
2323
<synopsis>
2424
[ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ]
25-
DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> ]
25+
DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
26+
[ FOR PORTION OF <replaceable class="parameter">range_name</replaceable> <replaceable class="parameter">for_portion_of_target</replaceable> ]
27+
[ [ AS ] <replaceable class="parameter">alias</replaceable> ]
2628
[ USING <replaceable class="parameter">from_item</replaceable> [, ...] ]
2729
[ WHERE <replaceable class="parameter">condition</replaceable> | WHERE CURRENT OF <replaceable class="parameter">cursor_name</replaceable> ]
2830
[ RETURNING [ WITH ( { OLD | NEW } AS <replaceable class="parameter">output_alias</replaceable> [, ...] ) ]
@@ -55,6 +57,43 @@ DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ *
5557
circumstances.
5658
</para>
5759

60+
<para>
61+
If the table has a range or multirange column,
62+
you may supply a <literal>FOR PORTION OF</literal> clause, and your delete will
63+
only affect rows that overlap the given interval. Furthermore, if a row's history
64+
extends outside the <literal>FOR PORTION OF</literal> bounds, then your delete
65+
will only change the history within those bounds. In effect you are deleting any
66+
moment targeted by <literal>FOR PORTION OF</literal> and no moments outside.
67+
</para>
68+
69+
<para>
70+
Specifically, after <productname>PostgreSQL</productname> deletes the existing row,
71+
it will <literal>INSERT</literal>
72+
new <glossterm linkend="glossary-temporal-leftovers">temporal leftovers</glossterm>:
73+
rows whose range or multirange receive the remaining history outside
74+
the targeted bounds, with un-updated values in their other columns.
75+
There will be zero to two inserted records,
76+
depending on whether the original history extended before the targeted
77+
<literal>FROM</literal>, after the targeted <literal>TO</literal>, both, or neither.
78+
Multiranges never require two temporal leftovers, because one value can always contain
79+
whatever history remains.
80+
</para>
81+
82+
<para>
83+
These secondary inserts fire <literal>INSERT</literal> triggers.
84+
Both <literal>STATEMENT</literal> and <literal>ROW</literal> triggers are fired.
85+
The <literal>BEFORE DELETE</literal> triggers are fired first, then
86+
<literal>BEFORE INSERT</literal>, then <literal>AFTER INSERT</literal>,
87+
then <literal>AFTER DELETE</literal>.
88+
</para>
89+
90+
<para>
91+
These secondary inserts do not require <literal>INSERT</literal> privilege on the table.
92+
This is because conceptually no new information has been added. The inserted rows only preserve
93+
existing data about the untargeted time period. Note this may result in users firing <literal>INSERT</literal>
94+
triggers who don't have insert privileges, so be careful about <literal>SECURITY DEFINER</literal> trigger functions!
95+
</para>
96+
5897
<para>
5998
The optional <literal>RETURNING</literal> clause causes <command>DELETE</command>
6099
to compute and return value(s) based on each row actually deleted.
@@ -117,6 +156,57 @@ DELETE FROM [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ *
117156
</listitem>
118157
</varlistentry>
119158

159+
<varlistentry>
160+
<term><replaceable class="parameter">range_name</replaceable></term>
161+
<listitem>
162+
<para>
163+
The range or multirange column to use when performing a temporal delete.
164+
</para>
165+
</listitem>
166+
</varlistentry>
167+
168+
<varlistentry>
169+
<term><replaceable class="parameter">for_portion_of_target</replaceable></term>
170+
<listitem>
171+
<para>
172+
The interval to delete. If you are targeting a range column,
173+
you may give this in the form <literal>FROM</literal>
174+
<replaceable class="parameter">start_time</replaceable> <literal>TO</literal>
175+
<replaceable class="parameter">end_time</replaceable>.
176+
Otherwise you must use
177+
<literal>(</literal><replaceable class="parameter">expression</replaceable><literal>)</literal>
178+
where the expression yields a value of the same type as
179+
<replaceable class="parameter">range_name</replaceable>.
180+
</para>
181+
</listitem>
182+
</varlistentry>
183+
184+
<varlistentry>
185+
<term><replaceable class="parameter">start_time</replaceable></term>
186+
<listitem>
187+
<para>
188+
The earliest time (inclusive) to change in a temporal delete.
189+
This must be a value matching the base type of the range from
190+
<replaceable class="parameter">range_name</replaceable>. A
191+
<literal>NULL</literal> here indicates a delete whose beginning is
192+
unbounded (as with range types).
193+
</para>
194+
</listitem>
195+
</varlistentry>
196+
197+
<varlistentry>
198+
<term><replaceable class="parameter">end_time</replaceable></term>
199+
<listitem>
200+
<para>
201+
The latest time (exclusive) to change in a temporal delete.
202+
This must be a value matching the base type of the range from
203+
<replaceable class="parameter">range_name</replaceable>. A
204+
<literal>NULL</literal> here indicates a delete whose end is unbounded
205+
(as with range types).
206+
</para>
207+
</listitem>
208+
</varlistentry>
209+
120210
<varlistentry>
121211
<term><replaceable class="parameter">from_item</replaceable></term>
122212
<listitem>
@@ -238,6 +328,10 @@ DELETE <replaceable class="parameter">count</replaceable>
238328
suppressed by a <literal>BEFORE DELETE</literal> trigger. If <replaceable
239329
class="parameter">count</replaceable> is 0, no rows were deleted by
240330
the query (this is not considered an error).
331+
If <literal>FOR PORTION OF</literal> was used, the
332+
<replaceable class="parameter">count</replaceable> also includes
333+
<glossterm linkend="glossary-temporal-leftovers">temporal leftovers</glossterm>
334+
that were inserted.
241335
</para>
242336

243337
<para>

0 commit comments

Comments
 (0)