v35-0003-Forbid-empty-ranges-multiranges-in-WITHOUT-OVERL.patch
text/x-patch
Filename: v35-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 v35-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/access/common/tupdesc.c | 23 | 0 |
| src/backend/catalog/heap.c | 35 | 0 |
| src/backend/catalog/index.c | 52 | 4 |
| src/backend/catalog/pg_constraint.c | 33 | 0 |
| src/backend/commands/indexcmds.c | 3 | 2 |
| src/backend/executor/execIndexing.c | 35 | 0 |
| src/backend/executor/execMain.c | 29 | 0 |
| src/backend/nodes/makefuncs.c | 3 | 1 |
| src/backend/parser/parse_utilcmd.c | 43 | 1 |
| src/backend/utils/cache/relcache.c | 112 | 1 |
| src/include/access/tupdesc.h | 2 | 0 |
| src/include/catalog/heap.h | 1 | 0 |
| src/include/catalog/pg_class.h | 3 | 0 |
| src/include/executor/executor.h | 2 | 0 |
| src/include/nodes/execnodes.h | 1 | 0 |
| src/include/nodes/makefuncs.h | 1 | 1 |
| src/test/regress/expected/without_overlaps.out | 122 | 4 |
| src/test/regress/sql/without_overlaps.sql | 68 | 0 |
From 93eaf434e8633e0787110261e81a200d9c7c7de7 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 v35 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.
Added pg_class.relperiods to track the number of WITHOUT OVERLAPS index
constraints on the table (similar to relchecks).
Increment relperiods when a WITHOUT OVERLAPS constraint is added, and
decrement it when removed.
In the relcache, if relperiods > 0 then get the attnos of all columns
used as the WITHOUT OVERLAPS part of a constraint.
Check for empty values in WITHOUT OVERLAPS columns when recording an
inserted/updated row.
Check for already-existing empties when adding a new temporal PK/UQ.
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/access/common/tupdesc.c | 23 ++++
src/backend/catalog/heap.c | 35 +++++
src/backend/catalog/index.c | 56 +++++++-
src/backend/catalog/pg_constraint.c | 33 +++++
src/backend/commands/indexcmds.c | 5 +-
src/backend/executor/execIndexing.c | 35 +++++
src/backend/executor/execMain.c | 29 ++++
src/backend/nodes/makefuncs.c | 4 +-
src/backend/parser/parse_utilcmd.c | 44 +++++-
src/backend/utils/cache/relcache.c | 113 +++++++++++++++-
src/include/access/tupdesc.h | 2 +
src/include/catalog/heap.h | 1 +
src/include/catalog/pg_class.h | 3 +
src/include/executor/executor.h | 2 +
src/include/nodes/execnodes.h | 1 +
src/include/nodes/makefuncs.h | 2 +-
.../regress/expected/without_overlaps.out | 126 +++++++++++++++++-
src/test/regress/sql/without_overlaps.sql | 68 ++++++++++
20 files changed, 574 insertions(+), 26 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 f2cda0c0e94..9844d23be40 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
<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/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 47379fef220..91ecdc71221 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -229,6 +229,15 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
}
}
+ if ((cpy->num_without_overlaps = constr->num_without_overlaps) > 0)
+ {
+ cpy->without_overlaps =
+ (AttrNumber *) palloc(cpy->num_without_overlaps * sizeof(AttrNumber));
+ memcpy(cpy->without_overlaps,
+ constr->without_overlaps,
+ cpy->num_without_overlaps * sizeof(AttrNumber));
+ }
+
desc->constr = cpy;
}
@@ -551,6 +560,20 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
check1->ccnoinherit == check2->ccnoinherit))
return false;
}
+
+ /*
+ * They should have the same number of periods,
+ * with the same attnos.
+ */
+ n = constr1->num_without_overlaps;
+ if (n != constr2->num_without_overlaps)
+ return false;
+ for (i = 0; i < n; i++)
+ {
+ if (constr1->without_overlaps[i] != constr2->without_overlaps[i])
+ return false;
+ }
+
}
else if (tupdesc2->constr != NULL)
return false;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8ea7768e244..49032c88717 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -935,6 +935,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
+ values[Anum_pg_class_relwithoutoverlaps - 1] = Int16GetDatum(rd_rel->relwithoutoverlaps);
values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(rd_rel->relrewrite);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
@@ -2689,6 +2690,40 @@ SetRelationNumChecks(Relation rel, int numchecks)
table_close(relrel, RowExclusiveLock);
}
+/*
+ * IncrementRelationWithoutOverlaps - Update the count of WITHOUT
+ * OVERLAPS constraints in the relation's pg_class tuple.
+ *
+ * Caller had better hold exclusive lock on the relation.
+ *
+ * An important side effect is that a SI update message will be sent out for
+ * the pg_class tuple, which will force other backends to rebuild their
+ * relcache entries for the rel. Also, this backend will rebuild its
+ * own relcache entry at the next CommandCounterIncrement.
+ */
+void
+IncrementRelationWithoutOverlaps(Relation rel)
+{
+ Relation relrel;
+ HeapTuple reltup;
+ Form_pg_class relStruct;
+
+ relrel = table_open(RelationRelationId, RowExclusiveLock);
+ reltup = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(reltup))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+ relStruct = (Form_pg_class) GETSTRUCT(reltup);
+
+ relStruct->relwithoutoverlaps = relStruct->relwithoutoverlaps + 1;
+
+ CatalogTupleUpdate(relrel, &reltup->t_self, reltup);
+
+ heap_freetuple(reltup);
+ table_close(relrel, RowExclusiveLock);
+}
+
/*
* Check for references to generated columns
*/
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5c48e5728e9..c635d36c025 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
@@ -1994,6 +1995,13 @@ index_constraint_create(Relation heapRelation,
ObjectAddressSet(idxaddr, RelationRelationId, indexRelationId);
recordDependencyOn(&idxaddr, &myself, DEPENDENCY_INTERNAL);
+ /*
+ * If this constraint has WITHOUT OVERLAPS,
+ * update relwithoutoverlaps in the table's pg_class record.
+ */
+ if (is_without_overlaps)
+ IncrementRelationWithoutOverlaps(heapRelation);
+
/*
* Also, if this is a constraint on a partition, give it partition-type
* dependencies on the parent constraint as well as the table.
@@ -2431,7 +2439,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 +2499,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++)
@@ -3148,6 +3158,9 @@ IndexCheckExclusion(Relation heapRelation,
EState *estate;
ExprContext *econtext;
Snapshot snapshot;
+ AttrNumber withoutOverlapsAttno = InvalidAttrNumber;
+ char withoutOverlapsTyptype = '\0';
+ Oid withoutOverlapsTypid = InvalidOid;
/*
* If we are reindexing the target index, mark it as no longer being
@@ -3157,6 +3170,27 @@ IndexCheckExclusion(Relation heapRelation,
if (ReindexIsCurrentlyProcessingIndex(RelationGetRelid(indexRelation)))
ResetReindexProcessing();
+ /*
+ * If this is for a WITHOUT OVERLAPS constraint,
+ * then we can check for empty ranges/multiranges in the same pass,
+ * rather than scanning the table all over again.
+ * Look up what we need about the WITHOUT OVERLAPS attribute.
+ */
+ if (indexInfo->ii_WithoutOverlaps)
+ {
+ TupleDesc tupdesc = RelationGetDescr(heapRelation);
+ Form_pg_attribute att;
+ TypeCacheEntry *typcache;
+
+ withoutOverlapsAttno = indexInfo->ii_IndexAttrNumbers[indexInfo->ii_NumIndexKeyAttrs - 1];
+ att = TupleDescAttr(tupdesc, withoutOverlapsAttno - 1);
+ typcache = lookup_type_cache(att->atttypid, 0);
+
+ withoutOverlapsTyptype = typcache->typtype;
+ withoutOverlapsTypid = att->atttypid;
+ }
+
+
/*
* Need an EState for evaluation of index expressions and partial-index
* predicates. Also a slot to hold the current tuple.
@@ -3195,6 +3229,21 @@ IndexCheckExclusion(Relation heapRelation,
continue;
}
+ if (indexInfo->ii_WithoutOverlaps)
+ {
+ bool attisnull;
+ Datum attval = slot_getattr(slot, withoutOverlapsAttno, &attisnull);
+ /* Nulls are allowed for UNIQUE but not PRIMARY KEY. */
+ if (attisnull)
+ continue;
+ /*
+ * Check that this tuple doesn't have an empty value.
+ */
+ ExecWithoutOverlapsNotEmpty(heapRelation, attval,
+ withoutOverlapsTyptype,
+ withoutOverlapsTypid);
+ }
+
/*
* Extract index column values, including computing expressions.
*/
@@ -3227,7 +3276,6 @@ IndexCheckExclusion(Relation heapRelation,
indexInfo->ii_PredicateState = NULL;
}
-
/*
* validate_index - support code for concurrent index builds
*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 9be050ccee8..c1d181a9d49 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -666,6 +666,39 @@ RemoveConstraintById(Oid conId)
table_close(pgrel, RowExclusiveLock);
}
+ /*
+ * Similarly we need to update the relwithoutoverlaps count if it is a check
+ * constraint PRIMARY KEY or UNIQUE constraint using WIHOUT OVERLAPS.
+ * This update will force backends to rebuild relcache entries when
+ * we commit.
+ */
+ else if (con->conperiod &&
+ (con->contype == CONSTRAINT_PRIMARY || con->contype == CONSTRAINT_UNIQUE))
+ {
+ Relation pgrel;
+ HeapTuple relTup;
+ Form_pg_class classForm;
+
+ pgrel = table_open(RelationRelationId, RowExclusiveLock);
+ relTup = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(con->conrelid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for relation %u",
+ con->conrelid);
+ classForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ if (classForm->relwithoutoverlaps == 0) /* should not happen */
+ elog(ERROR, "relation \"%s\" has relwithoutoverlaps = 0",
+ RelationGetRelationName(rel));
+ classForm->relwithoutoverlaps--;
+
+ CatalogTupleUpdate(pgrel, &relTup->t_self, relTup);
+
+ heap_freetuple(relTup);
+
+ table_close(pgrel, RowExclusiveLock);
+ }
+
/* Keep lock on constraint's rel until end of xact */
table_close(rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 558797b7d92..1464534f2d4 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);
@@ -915,7 +915,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..010f0ce3a9c 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() */
@@ -1097,3 +1099,36 @@ 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.
+ */
+void
+ExecWithoutOverlapsNotEmpty(Relation rel, Datum attval, Oid 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, "Got unknown type for WITHOUT OVERLAPS column: %d", atttypid);
+ }
+
+ /* Report a CHECK_VIOLATION */
+ if (isempty)
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row for relation \"%s\" contains empty WITHOUT OVERLAPS value",
+ RelationGetRelationName(rel))));
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4d7c92d63c1..8580ee192ec 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -59,6 +59,7 @@
#include "utils/partcache.h"
#include "utils/rls.h"
#include "utils/snapmgr.h"
+#include "utils/typcache.h"
/* Hooks for plugins to get control in ExecutorStart/Run/Finish/End */
@@ -1987,6 +1988,34 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
+ /*
+ * If there are WITHOUT OVERLAPS constraints,
+ * we must forbid empty range/multirange values.
+ */
+ if (constr->num_without_overlaps > 0)
+ {
+ uint16 i;
+
+ for (i = 0; i < constr->num_without_overlaps; i++)
+ {
+ AttrNumber attno = constr->without_overlaps[i];
+ Form_pg_attribute att;
+ TypeCacheEntry *typcache;
+ Datum attval;
+ bool isnull;
+
+ attval = slot_getattr(slot, attno, &isnull);
+ if (isnull)
+ continue;
+
+ att = TupleDescAttr(tupdesc, attno - 1);
+ typcache = lookup_type_cache(att->atttypid, 0);
+ ExecWithoutOverlapsNotEmpty(rel, attval,
+ typcache->typtype,
+ att->atttypid);
+ }
+ }
+
if (rel->rd_rel->relchecks > 0)
{
const char *failed;
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..b3b500efc46 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)
@@ -2475,6 +2477,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 +2527,45 @@ 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)
+ {
+ typid = attr->atttypid;
+ break;
+ }
+ }
+ }
+ if (found)
+ {
+ if (!OidIsValid(typid))
+ 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/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5e7a1499e1e..1e65db93455 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -307,6 +307,7 @@ static TupleDesc GetPgIndexDescriptor(void);
static void AttrDefaultFetch(Relation relation, int ndef);
static int AttrDefaultCmp(const void *a, const void *b);
static void CheckConstraintFetch(Relation relation);
+static void WithoutOverlapsFetch(Relation relation);
static int CheckConstraintCmp(const void *a, const void *b);
static void InitIndexAmRoutine(Relation relation);
static void IndexSupportInitialize(oidvector *indclass,
@@ -687,7 +688,8 @@ RelationBuildTupleDesc(Relation relation)
constr->has_generated_stored ||
ndef > 0 ||
attrmiss ||
- relation->rd_rel->relchecks > 0)
+ relation->rd_rel->relchecks > 0 ||
+ relation->rd_rel->relwithoutoverlaps > 0)
{
relation->rd_att->constr = constr;
@@ -702,6 +704,15 @@ RelationBuildTupleDesc(Relation relation)
CheckConstraintFetch(relation);
else
constr->num_check = 0;
+
+ /*
+ * Remember if any attributes have a PK or UNIQUE constraint
+ * using WITHOUT OVERLAPS. We must forbid empties for them.
+ */
+ if (relation->rd_rel->relwithoutoverlaps > 0) /* WITHOUT OVERLAPS */
+ WithoutOverlapsFetch(relation);
+ else
+ constr->num_without_overlaps = 0;
}
else
{
@@ -4662,6 +4673,106 @@ CheckConstraintFetch(Relation relation)
relation->rd_att->constr->num_check = found;
}
+/*
+ * Load any WITHOUT OVERLAPS attributes for the relation.
+ *
+ * These are not allowed to hold empty values.
+ */
+static void
+WithoutOverlapsFetch(Relation relation)
+{
+ Bitmapset *columns = NULL;
+ AttrNumber *result;
+ int nconstraints = relation->rd_rel->relwithoutoverlaps;
+ Relation conrel;
+ SysScanDesc conscan;
+ ScanKeyData skey[1];
+ HeapTuple htup;
+ int found = 0;
+ AttrNumber attno;
+
+ /* Search pg_constraint for relevant entries */
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+
+ conrel = table_open(ConstraintRelationId, AccessShareLock);
+ conscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(htup);
+ Datum val;
+ bool isnull;
+ ArrayType *arr;
+ int numcols;
+ int16 *attnums;
+
+ /* We want conperiod constraints only */
+ if (!conform->conperiod)
+ continue;
+
+ /* We want PRIMARY KEY and UNIQUE constraints only */
+ if (conform->contype != CONSTRAINT_PRIMARY &&
+ conform->contype != CONSTRAINT_UNIQUE)
+ continue;
+
+ /* protect limited size of array */
+ if (found >= nconstraints)
+ {
+ elog(WARNING, "unexpected pg_constraint record found for relation \"%s\"",
+ RelationGetRelationName(relation));
+ break;
+ }
+
+ /* Get the attno of the WITHOUT OVERLAPS column */
+ val = heap_getattr(htup, Anum_pg_constraint_conkey,
+ RelationGetDescr(conrel), &isnull);
+ if (isnull)
+ elog(ERROR, "found null conkey for WITHOUT OVERLAPS constraint");
+
+ arr = DatumGetArrayTypeP(val); /* ensure not toasted */
+ numcols = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numcols < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "conkey is not a 1-D smallint array");
+ attnums = (int16 *) ARR_DATA_PTR(arr);
+
+ /*
+ * Use a Bitmapset in case there are two constraints
+ * using the same WITHOUT OVERLAPS attribute.
+ */
+ columns = bms_add_member(columns, attnums[numcols - 1]);
+ found++;
+ }
+
+ systable_endscan(conscan);
+ table_close(conrel, AccessShareLock);
+
+ if (found != nconstraints)
+ elog(WARNING, "%d pg_constraint record(s) missing for relation \"%s\"",
+ nconstraints - found, RelationGetRelationName(relation));
+
+ /* Put everything we found into an array */
+ found = bms_num_members(columns);
+ result = (AttrNumber *)
+ MemoryContextAllocZero(CacheMemoryContext,
+ found * sizeof(AttrNumber));
+ attno = -1;
+ found = 0;
+ while ((attno = bms_next_member(columns, attno)) >= 0)
+ result[found++] = attno;
+ bms_free(columns);
+
+ /* Install array only after it's fully valid */
+ relation->rd_att->constr->without_overlaps = result;
+ relation->rd_att->constr->num_without_overlaps = found;
+}
+
/*
* qsort comparator to sort ConstrCheck entries by name
*/
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 8930a28d660..830cc15a4f0 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -38,9 +38,11 @@ typedef struct TupleConstr
{
AttrDefault *defval; /* array */
ConstrCheck *check; /* array */
+ AttrNumber *without_overlaps; /* array */
struct AttrMissing *missing; /* missing attributes values, NULL if none */
uint16 num_defval;
uint16 num_check;
+ uint16 num_without_overlaps;
bool has_not_null;
bool has_generated_stored;
} TupleConstr;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c512824cd1c..d5e99627f14 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -143,6 +143,7 @@ extern void CheckAttributeType(const char *attname,
Oid atttypid, Oid attcollation,
List *containing_rowtypes,
int flags);
+extern void IncrementRelationWithoutOverlaps(Relation rel);
/* pg_partitioned_table catalog manipulation functions */
extern void StorePartitionKey(Relation rel,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 0fc2c093b0d..2cbc3847ab8 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -119,6 +119,9 @@ CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,Relat
/* is relation a partition? */
bool relispartition BKI_DEFAULT(f);
+ /* # of WITHOUT OVERLAPS constraints for class */
+ int16 relwithoutoverlaps BKI_DEFAULT(0);
+
/* link to original rel during table rewrite; otherwise 0 */
Oid relrewrite BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_class);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 9770752ea3c..315dd225efc 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -642,6 +642,8 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
ItemPointer tupleid,
const Datum *values, const bool *isnull,
EState *estate, bool newIndex);
+extern void ExecWithoutOverlapsNotEmpty(Relation rel, Datum attval,
+ Oid typtype, Oid typtypid);
/*
* prototypes from functions in execReplication.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b62c96f2064..2298e9a1690 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 94234758598..dd9511ec05b 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 WITHOU...
+ ^
-- 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 OV...
+ ^
-- 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: new row for relation "temporal_rng" contains empty WITHOUT OVERLAPS value
+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: new row for relation "temporal_rng" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_rng" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_rng3" contains empty WITHOUT OVERLAPS value
+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: new row for relation "temporal_rng3" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_rng3" contains empty WITHOUT OVERLAPS value
+-- 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: new row for relation "temporal_rng3" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_mltrng" contains empty WITHOUT OVERLAPS value
+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: new row for relation "temporal_mltrng" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_mltrng" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_mltrng3" contains empty WITHOUT OVERLAPS value
+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: new row for relation "temporal_mltrng3" contains empty WITHOUT OVERLAPS value
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: new row for relation "temporal_mltrng3" contains empty WITHOUT OVERLAPS value
+-- 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: new row for relation "temporal_mltrng3" contains empty WITHOUT OVERLAPS value
SELECT * FROM temporal_mltrng3 ORDER BY id, valid_at;
id | valid_at
-------+---------------------------
@@ -844,6 +916,29 @@ CREATE TABLE temporal_partitioned (
) 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)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname IN ('temporal_partitioned', 'tp1', 'tp2') ORDER BY relname;
+ relname | relwithoutoverlaps
+----------------------+--------------------
+ temporal_partitioned | 1
+ tp1 | 1
+ tp2 | 1
+(3 rows)
+
+ALTER TABLE temporal_partitioned DETACH PARTITION tp1;
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ relname | relwithoutoverlaps
+---------+--------------------
+ tp1 | 1
+(1 row)
+
+ALTER TABLE tp1 DROP CONSTRAINT tp1_pkey;
+ALTER TABLE temporal_partitioned ATTACH PARTITION tp1 FOR VALUES IN ('[1,2)', '[2,3)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ relname | relwithoutoverlaps
+---------+--------------------
+ tp1 | 1
+(1 row)
+
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'),
@@ -879,6 +974,29 @@ CREATE TABLE temporal_partitioned (
) 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)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname IN ('temporal_partitioned', 'tp1', 'tp2') ORDER BY relname;
+ relname | relwithoutoverlaps
+----------------------+--------------------
+ temporal_partitioned | 1
+ tp1 | 1
+ tp2 | 1
+(3 rows)
+
+ALTER TABLE temporal_partitioned DETACH PARTITION tp1;
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ relname | relwithoutoverlaps
+---------+--------------------
+ tp1 | 1
+(1 row)
+
+ALTER TABLE tp1 DROP CONSTRAINT tp1_id_valid_at_excl;
+ALTER TABLE temporal_partitioned ATTACH PARTITION tp1 FOR VALUES IN ('[1,2)', '[2,3)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ relname | relwithoutoverlaps
+---------+--------------------
+ tp1 | 1
+(1 row)
+
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'),
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
index 36b3e6dc02a..841f5764a87 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;
@@ -607,6 +663,12 @@ CREATE TABLE temporal_partitioned (
) 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)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname IN ('temporal_partitioned', 'tp1', 'tp2') ORDER BY relname;
+ALTER TABLE temporal_partitioned DETACH PARTITION tp1;
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ALTER TABLE tp1 DROP CONSTRAINT tp1_pkey;
+ALTER TABLE temporal_partitioned ATTACH PARTITION tp1 FOR VALUES IN ('[1,2)', '[2,3)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
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'),
@@ -625,6 +687,12 @@ CREATE TABLE temporal_partitioned (
) 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)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname IN ('temporal_partitioned', 'tp1', 'tp2') ORDER BY relname;
+ALTER TABLE temporal_partitioned DETACH PARTITION tp1;
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
+ALTER TABLE tp1 DROP CONSTRAINT tp1_id_valid_at_excl;
+ALTER TABLE temporal_partitioned ATTACH PARTITION tp1 FOR VALUES IN ('[1,2)', '[2,3)');
+SELECT relname, relwithoutoverlaps FROM pg_class WHERE relname = 'tp1';
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'),
--
2.42.0