1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
-- Tests for WITHOUT OVERLAPS.
--
-- We leave behind several tables to test pg_dump etc:
-- temporal_rng, temporal_rng2,
-- temporal_fk_rng2rng.
SET datestyle TO ISO, YMD;
--
-- test input parser
--
-- PK with no columns just WITHOUT OVERLAPS:
CREATE TABLE temporal_rng (
valid_at daterange,
CONSTRAINT temporal_rng_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
);
ERROR: constraint using WITHOUT OVERLAPS needs at least two columns
-- PK with a range column/PERIOD that isn't there:
CREATE TABLE temporal_rng (
id INTEGER,
CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
ERROR: column "valid_at" named in key does not exist
LINE 3: CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOU...
^
-- PK with a non-range column:
CREATE TABLE temporal_rng (
id int4range,
valid_at TEXT,
CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
ERROR: data type text has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
-- PK with one column plus a range:
CREATE TABLE temporal_rng (
-- Since we can't depend on having btree_gist here,
-- use an int4range instead of an int.
-- (The rangetypes regression test uses the same trick.)
id int4range,
valid_at daterange,
CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
\d temporal_rng
Table "public.temporal_rng"
Column | Type | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
id | int4range | | not null |
valid_at | daterange | | not null |
Indexes:
"temporal_rng_pk" PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
pg_get_constraintdef
---------------------------------------------
PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
(1 row)
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
pg_get_indexdef
-------------------------------------------------------------------------------
CREATE UNIQUE INDEX temporal_rng_pk ON temporal_rng USING gist (id, valid_at)
(1 row)
-- PK with two columns plus a range:
-- We don't drop this table because tests below also need multiple scalar columns.
CREATE TABLE temporal_rng2 (
id1 int4range,
id2 int4range,
valid_at daterange,
CONSTRAINT temporal_rng2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
);
\d temporal_rng2
Table "public.temporal_rng2"
Column | Type | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
id1 | int4range | | not null |
id2 | int4range | | not null |
valid_at | daterange | | not null |
Indexes:
"temporal_rng2_pk" PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng2_pk';
pg_get_constraintdef
---------------------------------------------------
PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
(1 row)
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng2_pk';
pg_get_indexdef
---------------------------------------------------------------------------------------
CREATE UNIQUE INDEX temporal_rng2_pk ON temporal_rng2 USING gist (id1, id2, valid_at)
(1 row)
-- PK with a custom range type:
CREATE TYPE textrange2 AS range (subtype=text, collation="C");
CREATE TABLE temporal_rng3 (
id int4range,
valid_at textrange2,
CONSTRAINT temporal_rng3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
ALTER TABLE temporal_rng3 DROP CONSTRAINT temporal_rng3_pk;
DROP TABLE temporal_rng3;
DROP TYPE textrange2;
-- PK with a multirange:
CREATE TABLE temporal_mltrng (
id int4range,
valid_at tsmultirange,
CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
\d temporal_mltrng
Table "public.temporal_mltrng"
Column | Type | Collation | Nullable | Default
----------+--------------+-----------+----------+---------
id | int4range | | not null |
valid_at | tsmultirange | | not null |
Indexes:
"temporal_mltrng_pk" PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-- UNIQUE with no columns just WITHOUT OVERLAPS:
CREATE TABLE temporal_rng3 (
valid_at daterange,
CONSTRAINT temporal_rng3_uq UNIQUE (valid_at WITHOUT OVERLAPS)
);
ERROR: constraint using WITHOUT OVERLAPS needs at least two columns
-- UNIQUE with a range column/PERIOD that isn't there:
CREATE TABLE temporal_rng3 (
id INTEGER,
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
);
ERROR: column "valid_at" named in key does not exist
LINE 3: CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OV...
^
-- UNIQUE with a non-range column:
CREATE TABLE temporal_rng3 (
id int4range,
valid_at TEXT,
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
);
ERROR: data type text has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
-- UNIQUE with one column plus a range:
CREATE TABLE temporal_rng3 (
id int4range,
valid_at daterange,
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
);
\d temporal_rng3
Table "public.temporal_rng3"
Column | Type | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
id | int4range | | |
valid_at | daterange | | |
Indexes:
"temporal_rng3_uq" UNIQUE (id, valid_at WITHOUT OVERLAPS)
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
pg_get_constraintdef
----------------------------------------
UNIQUE (id, valid_at WITHOUT OVERLAPS)
(1 row)
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
pg_get_indexdef
---------------------------------------------------------------------------------
CREATE UNIQUE INDEX temporal_rng3_uq ON temporal_rng3 USING gist (id, valid_at)
(1 row)
DROP TABLE temporal_rng3;
-- UNIQUE with two columns plus a range:
CREATE TABLE temporal_rng3 (
id1 int4range,
id2 int4range,
valid_at daterange,
CONSTRAINT temporal_rng3_uq UNIQUE (id1, id2, valid_at WITHOUT OVERLAPS)
);
\d temporal_rng3
Table "public.temporal_rng3"
Column | Type | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
id1 | int4range | | |
id2 | int4range | | |
valid_at | daterange | | |
Indexes:
"temporal_rng3_uq" UNIQUE (id1, id2, valid_at WITHOUT OVERLAPS)
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
pg_get_constraintdef
----------------------------------------------
UNIQUE (id1, id2, valid_at WITHOUT OVERLAPS)
(1 row)
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
pg_get_indexdef
---------------------------------------------------------------------------------------
CREATE UNIQUE INDEX temporal_rng3_uq ON temporal_rng3 USING gist (id1, id2, valid_at)
(1 row)
DROP TABLE temporal_rng3;
-- UNIQUE with a custom range type:
CREATE TYPE textrange2 AS range (subtype=text, collation="C");
CREATE TABLE temporal_rng3 (
id int4range,
valid_at textrange2,
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
);
ALTER TABLE temporal_rng3 DROP CONSTRAINT temporal_rng3_uq;
DROP TABLE temporal_rng3;
DROP TYPE textrange2;
--
-- test ALTER TABLE ADD CONSTRAINT
--
DROP TABLE temporal_rng;
CREATE TABLE temporal_rng (
id int4range,
valid_at daterange
);
ALTER TABLE temporal_rng
ADD CONSTRAINT temporal_rng_pk
PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
-- PK with USING INDEX (not possible):
CREATE TABLE temporal3 (
id int4range,
valid_at daterange
);
CREATE INDEX idx_temporal3_uq ON temporal3 USING gist (id, valid_at);
ALTER TABLE temporal3
ADD CONSTRAINT temporal3_pk
PRIMARY KEY USING INDEX idx_temporal3_uq;
ERROR: "idx_temporal3_uq" is not a unique index
LINE 2: ADD CONSTRAINT temporal3_pk
^
DETAIL: Cannot create a primary key or unique constraint using such an index.
DROP TABLE temporal3;
-- UNIQUE with USING INDEX (not possible):
CREATE TABLE temporal3 (
id int4range,
valid_at daterange
);
CREATE INDEX idx_temporal3_uq ON temporal3 USING gist (id, valid_at);
ALTER TABLE temporal3
ADD CONSTRAINT temporal3_uq
UNIQUE USING INDEX idx_temporal3_uq;
ERROR: "idx_temporal3_uq" is not a unique index
LINE 2: ADD CONSTRAINT temporal3_uq
^
DETAIL: Cannot create a primary key or unique constraint using such an index.
DROP TABLE temporal3;
-- UNIQUE with USING [UNIQUE] INDEX (possible but not a temporal constraint):
CREATE TABLE temporal3 (
id int4range,
valid_at daterange
);
CREATE UNIQUE INDEX idx_temporal3_uq ON temporal3 (id, valid_at);
ALTER TABLE temporal3
ADD CONSTRAINT temporal3_uq
UNIQUE USING INDEX idx_temporal3_uq;
NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "idx_temporal3_uq" to "temporal3_uq"
DROP TABLE temporal3;
-- Add range column and the PK at the same time
CREATE TABLE temporal3 (
id int4range
);
ALTER TABLE temporal3
ADD COLUMN valid_at daterange,
ADD CONSTRAINT temporal3_pk
PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
DROP TABLE temporal3;
-- Add range column and UNIQUE constraint at the same time
CREATE TABLE temporal3 (
id int4range
);
ALTER TABLE temporal3
ADD COLUMN valid_at daterange,
ADD CONSTRAINT temporal3_uq
UNIQUE (id, valid_at WITHOUT OVERLAPS);
DROP TABLE temporal3;
--
-- test PK inserts
--
-- okay:
INSERT INTO temporal_rng (id, valid_at) VALUES ('[1,2)', daterange('2018-01-02', '2018-02-03'));
INSERT INTO temporal_rng (id, valid_at) VALUES ('[1,2)', daterange('2018-03-03', '2018-04-04'));
INSERT INTO temporal_rng (id, valid_at) VALUES ('[2,3)', daterange('2018-01-01', '2018-01-05'));
INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', daterange('2018-01-01', NULL));
-- should fail:
INSERT INTO temporal_rng (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
ERROR: conflicting key value violates exclusion constraint "temporal_rng_pk"
DETAIL: Key (id, valid_at)=([1,2), [2018-01-01,2018-01-05)) conflicts with existing key (id, valid_at)=([1,2), [2018-01-02,2018-02-03)).
INSERT INTO temporal_rng (id, valid_at) VALUES (NULL, daterange('2018-01-01', '2018-01-05'));
ERROR: null value in column "id" of relation "temporal_rng" violates not-null constraint
DETAIL: Failing row contains (null, [2018-01-01,2018-01-05)).
INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', NULL);
ERROR: null value in column "valid_at" of relation "temporal_rng" violates not-null constraint
DETAIL: Failing row contains ([3,4), null).
--
-- test a range with both a PK and a UNIQUE constraint
--
CREATE TABLE temporal3 (
id int4range,
valid_at daterange,
id2 int8range,
name TEXT,
CONSTRAINT temporal3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
CONSTRAINT temporal3_uniq UNIQUE (id2, valid_at WITHOUT OVERLAPS)
);
INSERT INTO temporal3 (id, valid_at, id2, name)
VALUES
('[1,2)', daterange('2000-01-01', '2010-01-01'), '[7,8)', 'foo'),
('[2,3)', daterange('2000-01-01', '2010-01-01'), '[9,10)', 'bar')
;
DROP TABLE temporal3;
--
-- test changing the PK's dependencies
--
CREATE TABLE temporal3 (
id int4range,
valid_at daterange,
CONSTRAINT temporal3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
);
ALTER TABLE temporal3 ALTER COLUMN valid_at DROP NOT NULL;
ERROR: column "valid_at" is in a primary key
ALTER TABLE temporal3 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
ALTER TABLE temporal3 RENAME COLUMN valid_at TO valid_thru;
ALTER TABLE temporal3 DROP COLUMN valid_thru;
DROP TABLE temporal3;
--
-- test PARTITION BY for ranges
--
-- temporal PRIMARY KEY:
CREATE TABLE temporal_partitioned (
id int4range,
valid_at daterange,
name text,
CONSTRAINT temporal_paritioned_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
) PARTITION BY LIST (id);
CREATE TABLE tp1 PARTITION OF temporal_partitioned FOR VALUES IN ('[1,2)', '[2,3)');
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,4)', '[4,5)');
INSERT INTO temporal_partitioned (id, valid_at, name) VALUES
('[1,2)', daterange('2000-01-01', '2000-02-01'), 'one'),
('[1,2)', daterange('2000-02-01', '2000-03-01'), 'one'),
('[3,4)', daterange('2000-01-01', '2010-01-01'), 'three');
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+-------
[1,2) | [2000-01-01,2000-02-01) | one
[1,2) | [2000-02-01,2000-03-01) | one
[3,4) | [2000-01-01,2010-01-01) | three
(3 rows)
SELECT * FROM tp1 ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+------
[1,2) | [2000-01-01,2000-02-01) | one
[1,2) | [2000-02-01,2000-03-01) | one
(2 rows)
SELECT * FROM tp2 ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+-------
[3,4) | [2000-01-01,2010-01-01) | three
(1 row)
DROP TABLE temporal_partitioned;
-- temporal UNIQUE:
CREATE TABLE temporal_partitioned (
id int4range,
valid_at daterange,
name text,
CONSTRAINT temporal_paritioned_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
) PARTITION BY LIST (id);
CREATE TABLE tp1 PARTITION OF temporal_partitioned FOR VALUES IN ('[1,2)', '[2,3)');
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,4)', '[4,5)');
INSERT INTO temporal_partitioned (id, valid_at, name) VALUES
('[1,2)', daterange('2000-01-01', '2000-02-01'), 'one'),
('[1,2)', daterange('2000-02-01', '2000-03-01'), 'one'),
('[3,4)', daterange('2000-01-01', '2010-01-01'), 'three');
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+-------
[1,2) | [2000-01-01,2000-02-01) | one
[1,2) | [2000-02-01,2000-03-01) | one
[3,4) | [2000-01-01,2010-01-01) | three
(3 rows)
SELECT * FROM tp1 ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+------
[1,2) | [2000-01-01,2000-02-01) | one
[1,2) | [2000-02-01,2000-03-01) | one
(2 rows)
SELECT * FROM tp2 ORDER BY id, valid_at;
id | valid_at | name
-------+-------------------------+-------
[3,4) | [2000-01-01,2010-01-01) | three
(1 row)
DROP TABLE temporal_partitioned;
RESET datestyle;
|