v39-0003-Forbid-empty-ranges-multiranges-in-WITHOUT-OVERL.patch
text/x-patch
Filename: v39-0003-Forbid-empty-ranges-multiranges-in-WITHOUT-OVERL.patch
Type: text/x-patch
Part: 2
Message:
Re: SQL:2011 application time
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v39-0003
Subject: Forbid empty ranges/multiranges in WITHOUT OVERLAPS columns
| File | + | − |
|---|---|---|
| doc/src/sgml/gist.sgml | 0 | 3 |
| doc/src/sgml/ref/create_table.sgml | 6 | 9 |
| src/backend/catalog/index.c | 6 | 4 |
| src/backend/commands/indexcmds.c | 3 | 2 |
| src/backend/executor/execIndexing.c | 64 | 0 |
| src/backend/nodes/makefuncs.c | 3 | 1 |
| src/backend/parser/parse_utilcmd.c | 47 | 1 |
| src/include/nodes/execnodes.h | 1 | 0 |
| src/include/nodes/makefuncs.h | 1 | 1 |
| src/test/regress/expected/without_overlaps.out | 76 | 4 |
| src/test/regress/sql/without_overlaps.sql | 56 | 0 |
From e1a8f04ea5cccb849f011a9482f58c734bf5988c Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 1 Jun 2024 15:24:56 -0700
Subject: [PATCH v39 3/8] Forbid empty ranges/multiranges in WITHOUT OVERLAPS
columns
Because 'empty' && 'empty' is false, the temporal PK/UQ constraint allows
duplicates, which is confusing to users and breaks internal
expectations. For instance when GROUP BY checks functional dependencies
on the PK, it allows selecting other columns from the table, but in the
presence of duplicate keys you could get the value from any of their
rows. So we need to forbid empties.
This all means we can only support ranges and multiranges for temporal
PK/UQs. So I added a check and updated the docs and tests.
---
doc/src/sgml/gist.sgml | 3 -
doc/src/sgml/ref/create_table.sgml | 15 ++--
src/backend/catalog/index.c | 10 ++-
src/backend/commands/indexcmds.c | 5 +-
src/backend/executor/execIndexing.c | 64 +++++++++++++++
src/backend/nodes/makefuncs.c | 4 +-
src/backend/parser/parse_utilcmd.c | 48 ++++++++++-
src/include/nodes/execnodes.h | 1 +
src/include/nodes/makefuncs.h | 2 +-
.../regress/expected/without_overlaps.out | 80 ++++++++++++++++++-
src/test/regress/sql/without_overlaps.sql | 56 +++++++++++++
11 files changed, 263 insertions(+), 25 deletions(-)
diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml
index dcf9433fa78..638d912dc2d 100644
--- a/doc/src/sgml/gist.sgml
+++ b/doc/src/sgml/gist.sgml
@@ -1186,9 +1186,6 @@ my_sortsupport(PG_FUNCTION_ARGS)
provides this function and it returns results for
<literal>RTEqualStrategyNumber</literal>, it can be used in the
non-<literal>WITHOUT OVERLAPS</literal> part(s) of an index constraint.
- If it returns results for <literal>RTOverlapStrategyNumber</literal>,
- the operator class can be used in the <literal>WITHOUT
- OVERLAPS</literal> part of an index constraint.
</para>
<para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8f5c72a4191..9243810c3fe 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -991,15 +991,12 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
example <literal>UNIQUE (id, valid_at WITHOUT OVERLAPS)</literal> behaves
like <literal>EXCLUDE USING GIST (id WITH =, valid_at WITH
&&)</literal>. The <literal>WITHOUT OVERLAPS</literal> column
- must have a range or multirange type. (Technically, any type is allowed
- whose default GiST opclass includes an overlaps operator. See the
- <literal>stratnum</literal> support function under <xref
- linkend="gist-extensibility"/> for details.) The non-<literal>WITHOUT
- OVERLAPS</literal> columns of the constraint can be any type that can be
- compared for equality in a GiST index. By default, only range types are
- supported, but you can use other types by adding the <xref
- linkend="btree-gist"/> extension (which is the expected way to use this
- feature).
+ must have a range or multirange type. Empty ranges/multiranges are
+ not permitted. The non-<literal>WITHOUT OVERLAPS</literal> columns of
+ the constraint can be any type that can be compared for equality in a
+ GiST index. By default, only range types are supported, but you can use
+ other types by adding the <xref linkend="btree-gist"/> extension (which
+ is the expected way to use this feature).
</para>
<para>
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5c48e5728e9..2ffdd7a3ccc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1394,7 +1394,8 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
oldInfo->ii_NullsNotDistinct,
false, /* not ready for inserts */
true,
- indexRelation->rd_indam->amsummarizing);
+ indexRelation->rd_indam->amsummarizing,
+ oldInfo->ii_WithoutOverlaps);
/*
* Extract the list of column names and the column numbers for the new
@@ -2431,7 +2432,8 @@ BuildIndexInfo(Relation index)
indexStruct->indnullsnotdistinct,
indexStruct->indisready,
false,
- index->rd_indam->amsummarizing);
+ index->rd_indam->amsummarizing,
+ indexStruct->indisexclusion && indexStruct->indisunique);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -2490,7 +2492,8 @@ BuildDummyIndexInfo(Relation index)
indexStruct->indnullsnotdistinct,
indexStruct->indisready,
false,
- index->rd_indam->amsummarizing);
+ index->rd_indam->amsummarizing,
+ indexStruct->indisexclusion && indexStruct->indisunique);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -3227,7 +3230,6 @@ IndexCheckExclusion(Relation heapRelation,
indexInfo->ii_PredicateState = NULL;
}
-
/*
* validate_index - support code for concurrent index builds
*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4d4dd0f61e6..9af592f25d6 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -242,7 +242,7 @@ CheckIndexCompatible(Oid oldId,
*/
indexInfo = makeIndexInfo(numberOfAttributes, numberOfAttributes,
accessMethodId, NIL, NIL, false, false,
- false, false, amsummarizing);
+ false, false, amsummarizing, isWithoutOverlaps);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
opclassIds = palloc_array(Oid, numberOfAttributes);
@@ -920,7 +920,8 @@ DefineIndex(Oid tableId,
stmt->nulls_not_distinct,
!concurrent,
concurrent,
- amissummarizing);
+ amissummarizing,
+ stmt->iswithoutoverlaps);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 59acf67a36a..263980cd547 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -114,6 +114,8 @@
#include "executor/executor.h"
#include "nodes/nodeFuncs.h"
#include "storage/lmgr.h"
+#include "utils/multirangetypes.h"
+#include "utils/rangetypes.h"
#include "utils/snapmgr.h"
/* waitMode argument to check_exclusion_or_unique_constraint() */
@@ -141,6 +143,8 @@ static bool index_unchanged_by_update(ResultRelInfo *resultRelInfo,
Relation indexRelation);
static bool index_expression_changed_walker(Node *node,
Bitmapset *allUpdatedCols);
+static void ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval,
+ char typtype, Oid atttypid);
/* ----------------------------------------------------------------
* ExecOpenIndices
@@ -720,6 +724,32 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
constr_strats = indexInfo->ii_UniqueStrats;
}
+ /*
+ * If this is a WITHOUT OVERLAPS constraint,
+ * we must also forbid empty ranges/multiranges.
+ * This must happen before we look for NULLs below,
+ * or a UNIQUE constraint could insert an empty
+ * range along with a NULL scalar part.
+ */
+ if (indexInfo->ii_WithoutOverlaps)
+ {
+ /*
+ * Look up the type from the heap tuple,
+ * but check the Datum from the index tuple.
+ */
+ AttrNumber attno = indexInfo->ii_IndexAttrNumbers[indnkeyatts - 1];
+
+ if (!isnull[indnkeyatts - 1])
+ {
+ TupleDesc tupdesc = RelationGetDescr(heap);
+ Form_pg_attribute att = TupleDescAttr(tupdesc, attno - 1);
+ TypeCacheEntry *typcache = lookup_type_cache(att->atttypid, 0);
+ ExecWithoutOverlapsNotEmpty(heap, att->attname,
+ values[indnkeyatts - 1],
+ typcache->typtype, att->atttypid);
+ }
+ }
+
/*
* If any of the input values are NULL, and the index uses the default
* nulls-are-distinct mode, the constraint check is assumed to pass (i.e.,
@@ -1097,3 +1127,37 @@ index_expression_changed_walker(Node *node, Bitmapset *allUpdatedCols)
return expression_tree_walker(node, index_expression_changed_walker,
(void *) allUpdatedCols);
}
+
+/*
+ * ExecWithoutOverlapsNotEmpty - raise an error if the tuple has an empty
+ * range or multirange in the given attribute.
+ */
+static void
+ExecWithoutOverlapsNotEmpty(Relation rel, NameData attname, Datum attval, char typtype, Oid atttypid)
+{
+ bool isempty;
+ RangeType *r;
+ MultirangeType *mr;
+
+ switch (typtype)
+ {
+ case TYPTYPE_RANGE:
+ r = DatumGetRangeTypeP(attval);
+ isempty = RangeIsEmpty(r);
+ break;
+ case TYPTYPE_MULTIRANGE:
+ mr = DatumGetMultirangeTypeP(attval);
+ isempty = MultirangeIsEmpty(mr);
+ break;
+ default:
+ elog(ERROR, "WITHOUT OVERLAPS column \"%s\" is not a range or multirange",
+ NameStr(attname));
+ }
+
+ /* Report a CHECK_VIOLATION */
+ if (isempty)
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("empty WITHOUT OVERLAPS value found in column \"%s\" in relation \"%s\"",
+ NameStr(attname), RelationGetRelationName(rel))));
+}
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 61ac172a857..9cac3c1c27b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -760,7 +760,8 @@ make_ands_implicit(Expr *clause)
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
List *predicates, bool unique, bool nulls_not_distinct,
- bool isready, bool concurrent, bool summarizing)
+ bool isready, bool concurrent, bool summarizing,
+ bool withoutoverlaps)
{
IndexInfo *n = makeNode(IndexInfo);
@@ -775,6 +776,7 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
n->ii_IndexUnchanged = false;
n->ii_Concurrent = concurrent;
n->ii_Summarizing = summarizing;
+ n->ii_WithoutOverlaps = withoutoverlaps;
/* summarizing indexes cannot contain non-key attributes */
Assert(!summarizing || (numkeyattrs == numattrs));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 53492c644a9..2a7f8fd154e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2397,7 +2397,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
* For UNIQUE and PRIMARY KEY, we just have a list of column names.
*
* Make sure referenced keys exist. If we are making a PRIMARY KEY index,
- * also make sure they are NOT NULL.
+ * also make sure they are NOT NULL. For WITHOUT OVERLAPS constraints,
+ * we make sure the last part is a range or multirange.
*/
else
{
@@ -2409,6 +2410,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
ColumnDef *column = NULL;
ListCell *columns;
IndexElem *iparam;
+ Oid typid = InvalidOid;
/* Make sure referenced column exists. */
foreach(columns, cxt->columns)
@@ -2420,6 +2422,9 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
break;
}
}
+ if (!found)
+ column = NULL;
+
if (found)
{
/*
@@ -2475,6 +2480,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
if (strcmp(key, inhname) == 0)
{
found = true;
+ typid = inhattr->atttypid;
/*
* It's tempting to set forced_not_null if the
@@ -2524,6 +2530,46 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
}
}
+ /* The WITHOUT OVERLAPS part (if any) must be a range or multirange type. */
+ if (constraint->without_overlaps && lc == list_last_cell(constraint->keys))
+ {
+ if (!found && cxt->isalter)
+ {
+ /*
+ * Look up the column type on existing table.
+ * If we can't find it, let things fail in DefineIndex.
+ */
+ Relation rel = cxt->rel;
+ for (int i = 0; i < rel->rd_att->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+ const char *attname;
+
+ if (attr->attisdropped)
+ break;
+
+ attname = NameStr(attr->attname);
+ if (strcmp(attname, key) == 0)
+ {
+ found = true;
+ typid = attr->atttypid;
+ break;
+ }
+ }
+ }
+ if (found)
+ {
+ if (!OidIsValid(typid) && column)
+ typid = typenameTypeId(NULL, column->typeName);
+
+ if (!OidIsValid(typid) || !(type_is_range(typid) || type_is_multirange(typid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" in WITHOUT OVERLAPS is not a range or multirange type", key),
+ parser_errposition(cxt->pstate, constraint->location)));
+ }
+ }
+
/* OK, add it to the index definition */
iparam = makeNode(IndexElem);
iparam->name = pstrdup(key);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index c3670f7158c..99d98e2de50 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -202,6 +202,7 @@ typedef struct IndexInfo
bool ii_Concurrent;
bool ii_BrokenHotChain;
bool ii_Summarizing;
+ bool ii_WithoutOverlaps;
int ii_ParallelWorkers;
Oid ii_Am;
void *ii_AmCache;
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5209d3de89c..0765e5c57b4 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -98,7 +98,7 @@ extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid,
List *expressions, List *predicates,
bool unique, bool nulls_not_distinct,
bool isready, bool concurrent,
- bool summarizing);
+ bool summarizing, bool withoutoverlaps);
extern Node *makeStringConst(char *str, int location);
extern DefElem *makeDefElem(char *name, Node *arg, int location);
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
index 19586c47b55..0fe3949f746 100644
--- a/src/test/regress/expected/without_overlaps.out
+++ b/src/test/regress/expected/without_overlaps.out
@@ -27,8 +27,9 @@ CREATE TABLE temporal_rng (
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.
+ERROR: column "valid_at" in WITHOUT OVERLAPS is not a range or multirange type
+LINE 4: CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHO...
+ ^
-- PK with one column plus a range:
CREATE TABLE temporal_rng (
-- Since we can't depend on having btree_gist here,
@@ -238,8 +239,9 @@ CREATE TABLE temporal_rng3 (
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.
+ERROR: column "valid_at" in WITHOUT OVERLAPS is not a range or multirange type
+LINE 4: CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT O...
+ ^
-- UNIQUE with one column plus a range:
CREATE TABLE temporal_rng3 (
id int4range,
@@ -393,6 +395,12 @@ BEGIN;
ERROR: could not create exclusion constraint "temporal_rng_pk"
DETAIL: Key (id, valid_at)=([1,2), [2018-01-02,2018-02-03)) conflicts with key (id, valid_at)=([1,2), [2018-01-01,2018-01-05)).
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', 'empty');
+ ALTER TABLE temporal_rng ADD CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng"
+ROLLBACK;
ALTER TABLE temporal_rng ADD CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_rng;
--
@@ -413,6 +421,9 @@ 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).
+-- rejects empty:
+INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', 'empty');
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng"
SELECT * FROM temporal_rng ORDER BY id, valid_at;
id | valid_at
-------+-------------------------
@@ -471,6 +482,12 @@ SET id = '[1,2)',
WHERE id = '[21,22)';
ERROR: null value in column "valid_at" of relation "temporal_rng" violates not-null constraint
DETAIL: Failing row contains ([1,2), null).
+-- rejects empty:
+UPDATE temporal_rng
+SET id = '[1,2)',
+ valid_at = 'empty'
+WHERE id = '[21,22)';
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng"
SELECT * FROM temporal_rng ORDER BY id, valid_at;
id | valid_at
---------+-------------------------
@@ -503,6 +520,12 @@ BEGIN;
ERROR: could not create exclusion constraint "temporal_rng3_uq"
DETAIL: Key (id, valid_at)=([1,2), [2018-01-02,2018-02-03)) conflicts with key (id, valid_at)=([1,2), [2018-01-01,2018-01-05)).
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', 'empty');
+ ALTER TABLE temporal_rng3 ADD CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng3"
+ROLLBACK;
ALTER TABLE temporal_rng3 ADD CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_rng3;
--
@@ -519,6 +542,9 @@ INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', NULL);
INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
ERROR: conflicting key value violates exclusion constraint "temporal_rng3_uq"
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)).
+-- rejects empty:
+INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', 'empty');
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng3"
SELECT * FROM temporal_rng3 ORDER BY id, valid_at;
id | valid_at
-------+-------------------------
@@ -576,6 +602,17 @@ SET valid_at = daterange('2018-03-01', '2018-05-05')
WHERE id = '[1,2)' AND valid_at IS NULL;
ERROR: conflicting key value violates exclusion constraint "temporal_rng3_uq"
DETAIL: Key (id, valid_at)=([1,2), [2018-03-01,2018-05-05)) conflicts with existing key (id, valid_at)=([1,2), [2018-03-03,2018-04-04)).
+-- rejects empty:
+UPDATE temporal_rng3
+SET valid_at = 'empty'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng3"
+-- still rejects empty when scalar part is NULL:
+UPDATE temporal_rng3
+SET id = NULL,
+ valid_at = 'empty'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_rng3"
SELECT * FROM temporal_rng3 ORDER BY id, valid_at;
id | valid_at
-------+-------------------------
@@ -606,6 +643,12 @@ BEGIN;
ERROR: could not create exclusion constraint "temporal_mltrng_pk"
DETAIL: Key (id, valid_at)=([1,2), {[2018-01-02,2018-02-03)}) conflicts with key (id, valid_at)=([1,2), {[2018-01-01,2018-01-05)}).
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', '{}');
+ ALTER TABLE temporal_mltrng ADD CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng"
+ROLLBACK;
ALTER TABLE temporal_mltrng ADD CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_mltrng;
--
@@ -626,6 +669,9 @@ DETAIL: Failing row contains (null, {[2018-01-01,2018-01-05)}).
INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', NULL);
ERROR: null value in column "valid_at" of relation "temporal_mltrng" violates not-null constraint
DETAIL: Failing row contains ([3,4), null).
+-- rejects empty:
+INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', '{}');
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng"
SELECT * FROM temporal_mltrng ORDER BY id, valid_at;
id | valid_at
-------+---------------------------
@@ -684,6 +730,12 @@ SET id = '[1,2)',
WHERE id = '[21,22)';
ERROR: null value in column "valid_at" of relation "temporal_mltrng" violates not-null constraint
DETAIL: Failing row contains ([1,2), null).
+-- rejects empty:
+UPDATE temporal_mltrng
+SET id = '[1,2)',
+ valid_at = '{}'
+WHERE id = '[21,22)';
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng"
SELECT * FROM temporal_mltrng ORDER BY id, valid_at;
id | valid_at
---------+---------------------------
@@ -716,6 +768,12 @@ BEGIN;
ERROR: could not create exclusion constraint "temporal_mltrng3_uq"
DETAIL: Key (id, valid_at)=([1,2), {[2018-01-02,2018-02-03)}) conflicts with key (id, valid_at)=([1,2), {[2018-01-01,2018-01-05)}).
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', '{}');
+ ALTER TABLE temporal_mltrng3 ADD CONSTRAINT temporal_mltrng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng3"
+ROLLBACK;
ALTER TABLE temporal_mltrng3 ADD CONSTRAINT temporal_mltrng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_mltrng3;
--
@@ -732,6 +790,9 @@ INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', NULL);
INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[1,2)', datemultirange(daterange('2018-01-01', '2018-01-05')));
ERROR: conflicting key value violates exclusion constraint "temporal_mltrng3_uq"
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)}).
+-- rejects empty:
+INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', '{}');
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng3"
SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
id | valid_at
-------+---------------------------
@@ -789,6 +850,17 @@ SET valid_at = datemultirange(daterange('2018-03-01', '2018-05-05'))
WHERE id = '[1,2)' AND valid_at IS NULL;
ERROR: conflicting key value violates exclusion constraint "temporal_mltrng3_uq"
DETAIL: Key (id, valid_at)=([1,2), {[2018-03-01,2018-05-05)}) conflicts with existing key (id, valid_at)=([1,2), {[2018-03-03,2018-04-04)}).
+-- rejects empty:
+UPDATE temporal_mltrng3
+SET valid_at = '{}'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng3"
+-- still rejects empty when scalar part is NULL:
+UPDATE temporal_mltrng3
+SET id = NULL,
+ valid_at = '{}'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+ERROR: empty WITHOUT OVERLAPS value found in column "valid_at" in relation "temporal_mltrng3"
SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
id | valid_at
-------+---------------------------
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
index cc7126b8531..e05fa1d00c0 100644
--- a/src/test/regress/sql/without_overlaps.sql
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -264,6 +264,11 @@ BEGIN;
INSERT INTO temporal_rng (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
ALTER TABLE temporal_rng ADD CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', 'empty');
+ ALTER TABLE temporal_rng ADD CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+ROLLBACK;
ALTER TABLE temporal_rng ADD CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_rng;
@@ -281,6 +286,8 @@ INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', daterange('2018-01-01',
INSERT INTO temporal_rng (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
INSERT INTO temporal_rng (id, valid_at) VALUES (NULL, daterange('2018-01-01', '2018-01-05'));
INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', NULL);
+-- rejects empty:
+INSERT INTO temporal_rng (id, valid_at) VALUES ('[3,4)', 'empty');
SELECT * FROM temporal_rng ORDER BY id, valid_at;
--
@@ -319,6 +326,11 @@ UPDATE temporal_rng
SET id = '[1,2)',
valid_at = NULL
WHERE id = '[21,22)';
+-- rejects empty:
+UPDATE temporal_rng
+SET id = '[1,2)',
+ valid_at = 'empty'
+WHERE id = '[21,22)';
SELECT * FROM temporal_rng ORDER BY id, valid_at;
--
@@ -345,6 +357,11 @@ BEGIN;
INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
ALTER TABLE temporal_rng3 ADD CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', 'empty');
+ ALTER TABLE temporal_rng3 ADD CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
+ROLLBACK;
ALTER TABLE temporal_rng3 ADD CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_rng3;
@@ -362,6 +379,8 @@ INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', NULL);
-- should fail:
INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[1,2)', daterange('2018-01-01', '2018-01-05'));
+-- rejects empty:
+INSERT INTO temporal_rng3 (id, valid_at) VALUES ('[3,4)', 'empty');
SELECT * FROM temporal_rng3 ORDER BY id, valid_at;
--
@@ -399,6 +418,15 @@ SELECT * FROM temporal_rng3 ORDER BY id, valid_at;
UPDATE temporal_rng3
SET valid_at = daterange('2018-03-01', '2018-05-05')
WHERE id = '[1,2)' AND valid_at IS NULL;
+-- rejects empty:
+UPDATE temporal_rng3
+SET valid_at = 'empty'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+-- still rejects empty when scalar part is NULL:
+UPDATE temporal_rng3
+SET id = NULL,
+ valid_at = 'empty'
+WHERE id = '[1,2)' AND valid_at IS NULL;
SELECT * FROM temporal_rng3 ORDER BY id, valid_at;
DROP TABLE temporal_rng3;
@@ -421,6 +449,11 @@ BEGIN;
INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[1,2)', datemultirange(daterange('2018-01-01', '2018-01-05')));
ALTER TABLE temporal_mltrng ADD CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', '{}');
+ ALTER TABLE temporal_mltrng ADD CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+ROLLBACK;
ALTER TABLE temporal_mltrng ADD CONSTRAINT temporal_mltrng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_mltrng;
@@ -438,6 +471,8 @@ INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', datemultirange(dater
INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[1,2)', datemultirange(daterange('2018-01-01', '2018-01-05')));
INSERT INTO temporal_mltrng (id, valid_at) VALUES (NULL, datemultirange(daterange('2018-01-01', '2018-01-05')));
INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', NULL);
+-- rejects empty:
+INSERT INTO temporal_mltrng (id, valid_at) VALUES ('[3,4)', '{}');
SELECT * FROM temporal_mltrng ORDER BY id, valid_at;
--
@@ -476,6 +511,11 @@ UPDATE temporal_mltrng
SET id = '[1,2)',
valid_at = NULL
WHERE id = '[21,22)';
+-- rejects empty:
+UPDATE temporal_mltrng
+SET id = '[1,2)',
+ valid_at = '{}'
+WHERE id = '[21,22)';
SELECT * FROM temporal_mltrng ORDER BY id, valid_at;
--
@@ -502,6 +542,11 @@ BEGIN;
INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[1,2)', datemultirange(daterange('2018-01-01', '2018-01-05')));
ALTER TABLE temporal_mltrng3 ADD CONSTRAINT temporal_mltrng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
ROLLBACK;
+-- rejects empty:
+BEGIN;
+ INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', '{}');
+ ALTER TABLE temporal_mltrng3 ADD CONSTRAINT temporal_mltrng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
+ROLLBACK;
ALTER TABLE temporal_mltrng3 ADD CONSTRAINT temporal_mltrng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS);
DELETE FROM temporal_mltrng3;
@@ -519,6 +564,8 @@ INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', NULL);
-- should fail:
INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[1,2)', datemultirange(daterange('2018-01-01', '2018-01-05')));
+-- rejects empty:
+INSERT INTO temporal_mltrng3 (id, valid_at) VALUES ('[3,4)', '{}');
SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
--
@@ -556,6 +603,15 @@ SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
UPDATE temporal_mltrng3
SET valid_at = datemultirange(daterange('2018-03-01', '2018-05-05'))
WHERE id = '[1,2)' AND valid_at IS NULL;
+-- rejects empty:
+UPDATE temporal_mltrng3
+SET valid_at = '{}'
+WHERE id = '[1,2)' AND valid_at IS NULL;
+-- still rejects empty when scalar part is NULL:
+UPDATE temporal_mltrng3
+SET id = NULL,
+ valid_at = '{}'
+WHERE id = '[1,2)' AND valid_at IS NULL;
SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
DROP TABLE temporal_mltrng3;
--
2.42.0