mirror of
https://github.com/postgres/postgres.git
synced 2025-05-13 01:13:08 -04:00
Add temporal PRIMARY KEY and UNIQUE constraints
Add WITHOUT OVERLAPS clause to PRIMARY KEY and UNIQUE constraints. These are backed by GiST indexes instead of B-tree indexes, since they are essentially exclusion constraints with = for the scalar parts of the key and && for the temporal part. Author: Paul A. Jungwirth <pj@illuminatedcomputing.com> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: jian he <jian.universality@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/CA+renyUApHgSZF9-nd-a0+OPGharLQLO=mDHcY4_qQ0+noCUVg@mail.gmail.com
This commit is contained in:
parent
74a7306310
commit
46a0cd4cef
@ -40,7 +40,7 @@ PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
|
||||
REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
|
||||
time timetz date interval macaddr macaddr8 inet cidr text varchar char \
|
||||
bytea bit varbit numeric uuid not_equal enum bool partitions \
|
||||
stratnum
|
||||
stratnum without_overlaps
|
||||
|
||||
SHLIB_LINK += $(filter -lm, $(LIBS))
|
||||
|
||||
|
44
contrib/btree_gist/expected/without_overlaps.out
Normal file
44
contrib/btree_gist/expected/without_overlaps.out
Normal file
@ -0,0 +1,44 @@
|
||||
-- Core must test WITHOUT OVERLAPS
|
||||
-- with an int4range + daterange,
|
||||
-- so here we do some simple tests
|
||||
-- to make sure int + daterange works too,
|
||||
-- since that is the expected use-case.
|
||||
CREATE TABLE temporal_rng (
|
||||
id integer,
|
||||
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 | integer | | 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)
|
||||
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2000-01-01,2001-01-01)');
|
||||
-- same key, doesn't overlap:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2001-01-01,2002-01-01)');
|
||||
-- overlaps but different key:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(2, '[2000-01-01,2001-01-01)');
|
||||
-- should fail:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2000-06-01,2001-01-01)');
|
||||
ERROR: conflicting key value violates exclusion constraint "temporal_rng_pk"
|
||||
DETAIL: Key (id, valid_at)=(1, [06-01-2000,01-01-2001)) conflicts with existing key (id, valid_at)=(1, [01-01-2000,01-01-2001)).
|
@ -91,6 +91,7 @@ tests += {
|
||||
'bool',
|
||||
'partitions',
|
||||
'stratnum',
|
||||
'without_overlaps',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
25
contrib/btree_gist/sql/without_overlaps.sql
Normal file
25
contrib/btree_gist/sql/without_overlaps.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- Core must test WITHOUT OVERLAPS
|
||||
-- with an int4range + daterange,
|
||||
-- so here we do some simple tests
|
||||
-- to make sure int + daterange works too,
|
||||
-- since that is the expected use-case.
|
||||
CREATE TABLE temporal_rng (
|
||||
id integer,
|
||||
valid_at daterange,
|
||||
CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
\d temporal_rng
|
||||
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
|
||||
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
|
||||
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2000-01-01,2001-01-01)');
|
||||
-- same key, doesn't overlap:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2001-01-01,2002-01-01)');
|
||||
-- overlaps but different key:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(2, '[2000-01-01,2001-01-01)');
|
||||
-- should fail:
|
||||
INSERT INTO temporal_rng VALUES
|
||||
(1, '[2000-06-01,2001-01-01)');
|
@ -2709,6 +2709,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
|
||||
</para></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry role="catalog_table_entry"><para role="column_definition">
|
||||
<structfield>conwithoutoverlaps</structfield> <type>bool</type>
|
||||
</para>
|
||||
<para>
|
||||
This constraint is defined with <literal>WITHOUT OVERLAPS</literal>
|
||||
(for primary keys and unique constraints).
|
||||
</para></entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry role="catalog_table_entry"><para role="column_definition">
|
||||
<structfield>conkey</structfield> <type>int2[]</type>
|
||||
|
@ -298,7 +298,8 @@ CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
|
||||
The optional twelfth method <function>stratnum</function> is used to
|
||||
translate well-known <literal>RT*StrategyNumber</literal>s (from
|
||||
<filename>src/include/access/stratnum.h</filename>) into strategy numbers
|
||||
used by the operator class.
|
||||
used by the operator class. This lets the core code look up operators for
|
||||
temporal constraint indexes.
|
||||
</para>
|
||||
|
||||
<variablelist>
|
||||
@ -1185,6 +1186,17 @@ my_sortsupport(PG_FUNCTION_ARGS)
|
||||
operator class has no matching strategy.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This is used for temporal index constraints (i.e., <literal>PRIMARY
|
||||
KEY</literal> and <literal>UNIQUE</literal>). If the operator class
|
||||
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>
|
||||
The <acronym>SQL</acronym> declaration of the function must look like
|
||||
this:
|
||||
|
@ -78,8 +78,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
|
||||
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
|
||||
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
|
||||
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
|
||||
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
|
||||
PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
|
||||
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">column_name</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
|
||||
PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">column_name</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
|
||||
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
|
||||
FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
|
||||
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
|
||||
@ -965,7 +965,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
|
||||
<varlistentry id="sql-createtable-parms-unique">
|
||||
<term><literal>UNIQUE [ NULLS [ NOT ] DISTINCT ]</literal> (column constraint)</term>
|
||||
<term><literal>UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
|
||||
<term><literal>UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">column_name</replaceable> WITHOUT OVERLAPS ] )</literal>
|
||||
<optional> <literal>INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...])</literal> </optional> (table constraint)</term>
|
||||
|
||||
<listitem>
|
||||
@ -979,6 +979,30 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
of these columns.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <literal>WITHOUT OVERLAPS</literal> option is specified for the
|
||||
last column, then that column is checked for overlaps instead of
|
||||
equality. In that case, the other columns of the constraint will allow
|
||||
duplicates so long as the duplicates don't overlap in the
|
||||
<literal>WITHOUT OVERLAPS</literal> column. (This is sometimes called a
|
||||
temporal key, if the column is a range of dates or timestamps, but
|
||||
PostgreSQL allows ranges over any base type.) In effect, such a
|
||||
constraint is enforced with an <literal>EXCLUDE</literal> constraint
|
||||
rather than a <literal>UNIQUE</literal> constraint. So for 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).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For the purpose of a unique constraint, null values are not
|
||||
considered equal, unless <literal>NULLS NOT DISTINCT</literal> is
|
||||
@ -1000,8 +1024,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Adding a unique constraint will automatically create a unique btree
|
||||
index on the column or group of columns used in the constraint.
|
||||
Adding a unique constraint will automatically create a unique B-tree
|
||||
index on the column or group of columns used in the constraint. But if
|
||||
the constraint includes a <literal>WITHOUT OVERLAPS</literal> clause, it
|
||||
will use a GiST index.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -1019,7 +1045,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
|
||||
<varlistentry id="sql-createtable-parms-primary-key">
|
||||
<term><literal>PRIMARY KEY</literal> (column constraint)</term>
|
||||
<term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
|
||||
<term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">column_name</replaceable> WITHOUT OVERLAPS ] )</literal>
|
||||
<optional> <literal>INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...])</literal> </optional> (table constraint)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
@ -1052,9 +1078,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Adding a <literal>PRIMARY KEY</literal> constraint will automatically
|
||||
create a unique btree index on the column or group of columns used in the
|
||||
constraint.
|
||||
As with a <literal>UNIQUE</literal> constraint, adding a
|
||||
<literal>PRIMARY KEY</literal> constraint will automatically create a
|
||||
unique B-tree index, or GiST if <literal>WITHOUT OVERLAPS</literal> was
|
||||
specified, on the column or group of columns used in the constraint.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -1070,3 +1070,32 @@ gist_stratnum_identity(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_UINT16(strat);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the opclass's private stratnum used for the given strategy.
|
||||
*
|
||||
* Calls the opclass's GIST_STRATNUM_PROC support function, if any,
|
||||
* and returns the result.
|
||||
* Returns InvalidStrategy if the function is not defined.
|
||||
*/
|
||||
StrategyNumber
|
||||
GistTranslateStratnum(Oid opclass, StrategyNumber strat)
|
||||
{
|
||||
Oid opfamily;
|
||||
Oid opcintype;
|
||||
Oid funcid;
|
||||
Datum result;
|
||||
|
||||
/* Look up the opclass family and input datatype. */
|
||||
if (!get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
|
||||
return InvalidStrategy;
|
||||
|
||||
/* Check whether the function is provided. */
|
||||
funcid = get_opfamily_proc(opfamily, opcintype, opcintype, GIST_STRATNUM_PROC);
|
||||
if (!OidIsValid(funcid))
|
||||
return InvalidStrategy;
|
||||
|
||||
/* Ask the translation function */
|
||||
result = OidFunctionCall1Coll(funcid, InvalidOid, UInt16GetDatum(strat));
|
||||
return DatumGetUInt16(result);
|
||||
}
|
||||
|
@ -2141,6 +2141,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
|
||||
is_local, /* conislocal */
|
||||
inhcount, /* coninhcount */
|
||||
is_no_inherit, /* connoinherit */
|
||||
false, /* conwithoutoverlaps */
|
||||
is_internal); /* internally constructed? */
|
||||
|
||||
pfree(ccbin);
|
||||
@ -2191,6 +2192,7 @@ StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum,
|
||||
is_local,
|
||||
inhcount,
|
||||
is_no_inherit,
|
||||
false, /* conwithoutoverlaps */
|
||||
false);
|
||||
return constrOid;
|
||||
}
|
||||
|
@ -1904,6 +1904,7 @@ index_concurrently_set_dead(Oid heapId, Oid indexId)
|
||||
* INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row
|
||||
* INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies
|
||||
* of index on table's columns
|
||||
* INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS: constraint uses WITHOUT OVERLAPS
|
||||
* allow_system_table_mods: allow table to be a system catalog
|
||||
* is_internal: index is constructed due to internal process
|
||||
*/
|
||||
@ -1927,11 +1928,13 @@ index_constraint_create(Relation heapRelation,
|
||||
bool mark_as_primary;
|
||||
bool islocal;
|
||||
bool noinherit;
|
||||
bool is_without_overlaps;
|
||||
int inhcount;
|
||||
|
||||
deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
|
||||
initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
|
||||
mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;
|
||||
is_without_overlaps = (constr_flags & INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS) != 0;
|
||||
|
||||
/* constraint creation support doesn't work while bootstrapping */
|
||||
Assert(!IsBootstrapProcessingMode());
|
||||
@ -2008,6 +2011,7 @@ index_constraint_create(Relation heapRelation,
|
||||
islocal,
|
||||
inhcount,
|
||||
noinherit,
|
||||
is_without_overlaps,
|
||||
is_internal);
|
||||
|
||||
/*
|
||||
|
@ -78,6 +78,7 @@ CreateConstraintEntry(const char *constraintName,
|
||||
bool conIsLocal,
|
||||
int conInhCount,
|
||||
bool conNoInherit,
|
||||
bool conWithoutOverlaps,
|
||||
bool is_internal)
|
||||
{
|
||||
Relation conDesc;
|
||||
@ -193,6 +194,7 @@ CreateConstraintEntry(const char *constraintName,
|
||||
values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
|
||||
values[Anum_pg_constraint_coninhcount - 1] = Int16GetDatum(conInhCount);
|
||||
values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
|
||||
values[Anum_pg_constraint_conwithoutoverlaps - 1] = BoolGetDatum(conWithoutOverlaps);
|
||||
|
||||
if (conkeyArray)
|
||||
values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/amapi.h"
|
||||
#include "access/gist.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/reloptions.h"
|
||||
@ -86,6 +87,7 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
Oid accessMethodId,
|
||||
bool amcanorder,
|
||||
bool isconstraint,
|
||||
bool iswithoutoverlaps,
|
||||
Oid ddl_userid,
|
||||
int ddl_sec_context,
|
||||
int *ddl_save_nestlevel);
|
||||
@ -144,6 +146,7 @@ typedef struct ReindexErrorInfo
|
||||
* to index on.
|
||||
* 'exclusionOpNames': list of names of exclusion-constraint operators,
|
||||
* or NIL if not an exclusion constraint.
|
||||
* 'isWithoutOverlaps': true iff this index has a WITHOUT OVERLAPS clause.
|
||||
*
|
||||
* This is tailored to the needs of ALTER TABLE ALTER TYPE, which recreates
|
||||
* any indexes that depended on a changing column from their pg_get_indexdef
|
||||
@ -173,7 +176,8 @@ bool
|
||||
CheckIndexCompatible(Oid oldId,
|
||||
const char *accessMethodName,
|
||||
const List *attributeList,
|
||||
const List *exclusionOpNames)
|
||||
const List *exclusionOpNames,
|
||||
bool isWithoutOverlaps)
|
||||
{
|
||||
bool isconstraint;
|
||||
Oid *typeIds;
|
||||
@ -248,8 +252,8 @@ CheckIndexCompatible(Oid oldId,
|
||||
coloptions, attributeList,
|
||||
exclusionOpNames, relationId,
|
||||
accessMethodName, accessMethodId,
|
||||
amcanorder, isconstraint, InvalidOid, 0, NULL);
|
||||
|
||||
amcanorder, isconstraint, isWithoutOverlaps, InvalidOid,
|
||||
0, NULL);
|
||||
|
||||
/* Get the soon-obsolete pg_index tuple. */
|
||||
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldId));
|
||||
@ -559,6 +563,7 @@ DefineIndex(Oid tableId,
|
||||
bool amcanorder;
|
||||
bool amissummarizing;
|
||||
amoptions_function amoptions;
|
||||
bool exclusion;
|
||||
bool partitioned;
|
||||
bool safe_index;
|
||||
Datum reloptions;
|
||||
@ -677,6 +682,12 @@ DefineIndex(Oid tableId,
|
||||
|
||||
namespaceId = RelationGetNamespace(rel);
|
||||
|
||||
/*
|
||||
* It has exclusion constraint behavior if it's an EXCLUDE constraint or a
|
||||
* temporal PRIMARY KEY/UNIQUE constraint
|
||||
*/
|
||||
exclusion = stmt->excludeOpNames || stmt->iswithoutoverlaps;
|
||||
|
||||
/* Ensure that it makes sense to index this kind of relation */
|
||||
switch (rel->rd_rel->relkind)
|
||||
{
|
||||
@ -845,7 +856,7 @@ DefineIndex(Oid tableId,
|
||||
pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
|
||||
accessMethodId);
|
||||
|
||||
if (stmt->unique && !amRoutine->amcanunique)
|
||||
if (stmt->unique && !stmt->iswithoutoverlaps && !amRoutine->amcanunique)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("access method \"%s\" does not support unique indexes",
|
||||
@ -860,7 +871,7 @@ DefineIndex(Oid tableId,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("access method \"%s\" does not support multicolumn indexes",
|
||||
accessMethodName)));
|
||||
if (stmt->excludeOpNames && amRoutine->amgettuple == NULL)
|
||||
if (exclusion && amRoutine->amgettuple == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("access method \"%s\" does not support exclusion constraints",
|
||||
@ -913,8 +924,9 @@ DefineIndex(Oid tableId,
|
||||
coloptions, allIndexParams,
|
||||
stmt->excludeOpNames, tableId,
|
||||
accessMethodName, accessMethodId,
|
||||
amcanorder, stmt->isconstraint, root_save_userid,
|
||||
root_save_sec_context, &root_save_nestlevel);
|
||||
amcanorder, stmt->isconstraint, stmt->iswithoutoverlaps,
|
||||
root_save_userid, root_save_sec_context,
|
||||
&root_save_nestlevel);
|
||||
|
||||
/*
|
||||
* Extra checks when creating a PRIMARY KEY index.
|
||||
@ -932,7 +944,7 @@ DefineIndex(Oid tableId,
|
||||
* We could lift this limitation if we had global indexes, but those have
|
||||
* their own problems, so this is a useful feature combination.
|
||||
*/
|
||||
if (partitioned && (stmt->unique || stmt->excludeOpNames))
|
||||
if (partitioned && (stmt->unique || exclusion))
|
||||
{
|
||||
PartitionKey key = RelationGetPartitionKey(rel);
|
||||
const char *constraint_type;
|
||||
@ -986,10 +998,10 @@ DefineIndex(Oid tableId,
|
||||
* associated with index columns, too. We know what to do with
|
||||
* btree opclasses; if there are ever any other index types that
|
||||
* support unique indexes, this logic will need extension. But if
|
||||
* we have an exclusion constraint, it already knows the
|
||||
* operators, so we don't have to infer them.
|
||||
* we have an exclusion constraint (or a temporal PK), it already
|
||||
* knows the operators, so we don't have to infer them.
|
||||
*/
|
||||
if (stmt->unique && accessMethodId != BTREE_AM_OID)
|
||||
if (stmt->unique && !stmt->iswithoutoverlaps && accessMethodId != BTREE_AM_OID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot match partition key to an index using access method \"%s\"",
|
||||
@ -1028,12 +1040,12 @@ DefineIndex(Oid tableId,
|
||||
{
|
||||
Oid idx_eqop = InvalidOid;
|
||||
|
||||
if (stmt->unique)
|
||||
if (stmt->unique && !stmt->iswithoutoverlaps)
|
||||
idx_eqop = get_opfamily_member(idx_opfamily,
|
||||
idx_opcintype,
|
||||
idx_opcintype,
|
||||
BTEqualStrategyNumber);
|
||||
else if (stmt->excludeOpNames)
|
||||
else if (exclusion)
|
||||
idx_eqop = indexInfo->ii_ExclusionOps[j];
|
||||
Assert(idx_eqop);
|
||||
|
||||
@ -1042,7 +1054,7 @@ DefineIndex(Oid tableId,
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
else if (stmt->excludeOpNames)
|
||||
else if (exclusion)
|
||||
{
|
||||
/*
|
||||
* We found a match, but it's not an equality
|
||||
@ -1186,6 +1198,8 @@ DefineIndex(Oid tableId,
|
||||
constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
|
||||
if (stmt->initdeferred)
|
||||
constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
|
||||
if (stmt->iswithoutoverlaps)
|
||||
constr_flags |= INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS;
|
||||
|
||||
indexRelationId =
|
||||
index_create(rel, indexRelationName, indexRelationId, parentIndexId,
|
||||
@ -1850,6 +1864,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
Oid accessMethodId,
|
||||
bool amcanorder,
|
||||
bool isconstraint,
|
||||
bool iswithoutoverlaps,
|
||||
Oid ddl_userid,
|
||||
int ddl_sec_context,
|
||||
int *ddl_save_nestlevel)
|
||||
@ -1873,6 +1888,14 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
else
|
||||
nextExclOp = NULL;
|
||||
|
||||
/* exclusionOpNames can be non-NIL if we are creating a partition */
|
||||
if (iswithoutoverlaps && exclusionOpNames == NIL)
|
||||
{
|
||||
indexInfo->ii_ExclusionOps = palloc_array(Oid, nkeycols);
|
||||
indexInfo->ii_ExclusionProcs = palloc_array(Oid, nkeycols);
|
||||
indexInfo->ii_ExclusionStrats = palloc_array(uint16, nkeycols);
|
||||
}
|
||||
|
||||
if (OidIsValid(ddl_userid))
|
||||
GetUserIdAndSecContext(&save_userid, &save_sec_context);
|
||||
|
||||
@ -2149,6 +2172,21 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
|
||||
indexInfo->ii_ExclusionStrats[attn] = strat;
|
||||
nextExclOp = lnext(exclusionOpNames, nextExclOp);
|
||||
}
|
||||
else if (iswithoutoverlaps)
|
||||
{
|
||||
StrategyNumber strat;
|
||||
Oid opid;
|
||||
|
||||
if (attn == nkeycols - 1)
|
||||
strat = RTOverlapStrategyNumber;
|
||||
else
|
||||
strat = RTEqualStrategyNumber;
|
||||
GetOperatorFromWellKnownStrategy(opclassOids[attn], atttype,
|
||||
&opid, &strat);
|
||||
indexInfo->ii_ExclusionOps[attn] = opid;
|
||||
indexInfo->ii_ExclusionProcs[attn] = get_opcode(opid);
|
||||
indexInfo->ii_ExclusionStrats[attn] = strat;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up the per-column options (indoption field). For now, this is
|
||||
@ -2379,6 +2417,83 @@ GetDefaultOpClass(Oid type_id, Oid am_id)
|
||||
return InvalidOid;
|
||||
}
|
||||
|
||||
/*
|
||||
* GetOperatorFromWellKnownStrategy
|
||||
*
|
||||
* opclass - the opclass to use
|
||||
* atttype - the type to ask about
|
||||
* opid - holds the operator we found
|
||||
* strat - holds the input and output strategy number
|
||||
*
|
||||
* Finds an operator from a "well-known" strategy number. This is used for
|
||||
* temporal index constraints (and other temporal features) to look up
|
||||
* equality and overlaps operators, since the strategy numbers for non-btree
|
||||
* indexams need not follow any fixed scheme. We ask an opclass support
|
||||
* function to translate from the well-known number to the internal value. If
|
||||
* the function isn't defined or it gives no result, we return
|
||||
* InvalidStrategy.
|
||||
*/
|
||||
void
|
||||
GetOperatorFromWellKnownStrategy(Oid opclass, Oid atttype,
|
||||
Oid *opid, StrategyNumber *strat)
|
||||
{
|
||||
Oid opfamily;
|
||||
Oid opcintype;
|
||||
StrategyNumber instrat = *strat;
|
||||
|
||||
Assert(instrat == RTEqualStrategyNumber || instrat == RTOverlapStrategyNumber);
|
||||
|
||||
*opid = InvalidOid;
|
||||
|
||||
if (get_opclass_opfamily_and_input_type(opclass, &opfamily, &opcintype))
|
||||
{
|
||||
/*
|
||||
* Ask the opclass to translate to its internal stratnum
|
||||
*
|
||||
* For now we only need GiST support, but this could support other
|
||||
* indexams if we wanted.
|
||||
*/
|
||||
*strat = GistTranslateStratnum(opclass, instrat);
|
||||
if (*strat == InvalidStrategy)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
|
||||
tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for operator class %u", opclass);
|
||||
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
instrat == RTEqualStrategyNumber ?
|
||||
errmsg("could not identify an equality operator for type %s", format_type_be(atttype)) :
|
||||
errmsg("could not identify an overlaps operator for type %s", format_type_be(atttype)),
|
||||
errdetail("Could not translate strategy number %d for operator class \"%s\" for access method \"%s\".",
|
||||
instrat, NameStr(((Form_pg_opclass) GETSTRUCT(tuple))->opcname), "gist"));
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
|
||||
*opid = get_opfamily_member(opfamily, opcintype, opcintype, *strat);
|
||||
}
|
||||
|
||||
if (!OidIsValid(*opid))
|
||||
{
|
||||
HeapTuple tuple;
|
||||
|
||||
tuple = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamily));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for operator family %u", opfamily);
|
||||
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
instrat == RTEqualStrategyNumber ?
|
||||
errmsg("could not identify an equality operator for type %s", format_type_be(atttype)) :
|
||||
errmsg("could not identify an overlaps operator for type %s", format_type_be(atttype)),
|
||||
errdetail("There is no suitable operator in operator family \"%s\" for access method \"%s\".",
|
||||
NameStr(((Form_pg_opfamily) GETSTRUCT(tuple))->opfname), "gist"));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* makeObjectName()
|
||||
*
|
||||
|
@ -10340,6 +10340,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
|
||||
conislocal, /* islocal */
|
||||
coninhcount, /* inhcount */
|
||||
connoinherit, /* conNoInherit */
|
||||
false, /* conWithoutOverlaps */
|
||||
false); /* is_internal */
|
||||
|
||||
ObjectAddressSet(address, ConstraintRelationId, constrOid);
|
||||
@ -10638,6 +10639,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
|
||||
false,
|
||||
1,
|
||||
false,
|
||||
false, /* conWithoutOverlaps */
|
||||
false);
|
||||
|
||||
/*
|
||||
@ -11143,6 +11145,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
|
||||
false, /* islocal */
|
||||
1, /* inhcount */
|
||||
false, /* conNoInherit */
|
||||
false, /* conWithoutOverlaps */
|
||||
true);
|
||||
|
||||
/* Set up partition dependencies for the new constraint */
|
||||
@ -14556,7 +14559,8 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
|
||||
if (CheckIndexCompatible(oldId,
|
||||
stmt->accessMethod,
|
||||
stmt->indexParams,
|
||||
stmt->excludeOpNames))
|
||||
stmt->excludeOpNames,
|
||||
stmt->iswithoutoverlaps))
|
||||
{
|
||||
Relation irel = index_open(oldId, NoLock);
|
||||
|
||||
|
@ -841,6 +841,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
|
||||
true, /* islocal */
|
||||
0, /* inhcount */
|
||||
true, /* noinherit */
|
||||
false, /* conwithoutoverlaps */
|
||||
isInternal); /* is_internal */
|
||||
}
|
||||
|
||||
|
@ -3544,6 +3544,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
|
||||
true, /* is local */
|
||||
0, /* inhcount */
|
||||
false, /* connoinherit */
|
||||
false, /* conwithoutoverlaps */
|
||||
false); /* is_internal */
|
||||
if (constrAddr)
|
||||
ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
|
||||
|
@ -756,6 +756,7 @@ _outConstraint(StringInfo str, const Constraint *node)
|
||||
case CONSTR_PRIMARY:
|
||||
appendStringInfoString(str, "PRIMARY_KEY");
|
||||
WRITE_NODE_FIELD(keys);
|
||||
WRITE_BOOL_FIELD(without_overlaps);
|
||||
WRITE_NODE_FIELD(including);
|
||||
WRITE_NODE_FIELD(options);
|
||||
WRITE_STRING_FIELD(indexname);
|
||||
@ -768,6 +769,7 @@ _outConstraint(StringInfo str, const Constraint *node)
|
||||
appendStringInfoString(str, "UNIQUE");
|
||||
WRITE_BOOL_FIELD(nulls_not_distinct);
|
||||
WRITE_NODE_FIELD(keys);
|
||||
WRITE_BOOL_FIELD(without_overlaps);
|
||||
WRITE_NODE_FIELD(including);
|
||||
WRITE_NODE_FIELD(options);
|
||||
WRITE_STRING_FIELD(indexname);
|
||||
|
@ -427,6 +427,7 @@ _readConstraint(void)
|
||||
|
||||
case CONSTR_PRIMARY:
|
||||
READ_NODE_FIELD(keys);
|
||||
READ_BOOL_FIELD(without_overlaps);
|
||||
READ_NODE_FIELD(including);
|
||||
READ_NODE_FIELD(options);
|
||||
READ_STRING_FIELD(indexname);
|
||||
@ -438,6 +439,7 @@ _readConstraint(void)
|
||||
case CONSTR_UNIQUE:
|
||||
READ_BOOL_FIELD(nulls_not_distinct);
|
||||
READ_NODE_FIELD(keys);
|
||||
READ_BOOL_FIELD(without_overlaps);
|
||||
READ_NODE_FIELD(including);
|
||||
READ_NODE_FIELD(options);
|
||||
READ_STRING_FIELD(indexname);
|
||||
|
@ -529,7 +529,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
columnref in_expr having_clause func_table xmltable array_expr
|
||||
OptWhereClause operator_def_arg
|
||||
%type <list> rowsfrom_item rowsfrom_list opt_col_def_list
|
||||
%type <boolean> opt_ordinality
|
||||
%type <boolean> opt_ordinality opt_without_overlaps
|
||||
%type <list> ExclusionConstraintList ExclusionConstraintElem
|
||||
%type <list> func_arg_list func_arg_list_opt
|
||||
%type <node> func_arg_expr
|
||||
@ -4133,7 +4133,7 @@ ConstraintElem:
|
||||
n->initially_valid = true;
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| UNIQUE opt_unique_null_treatment '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
|
||||
| UNIQUE opt_unique_null_treatment '(' columnList opt_without_overlaps ')' opt_c_include opt_definition OptConsTableSpace
|
||||
ConstraintAttributeSpec
|
||||
{
|
||||
Constraint *n = makeNode(Constraint);
|
||||
@ -4142,11 +4142,12 @@ ConstraintElem:
|
||||
n->location = @1;
|
||||
n->nulls_not_distinct = !$2;
|
||||
n->keys = $4;
|
||||
n->including = $6;
|
||||
n->options = $7;
|
||||
n->without_overlaps = $5;
|
||||
n->including = $7;
|
||||
n->options = $8;
|
||||
n->indexname = NULL;
|
||||
n->indexspace = $8;
|
||||
processCASbits($9, @9, "UNIQUE",
|
||||
n->indexspace = $9;
|
||||
processCASbits($10, @10, "UNIQUE",
|
||||
&n->deferrable, &n->initdeferred, NULL,
|
||||
NULL, yyscanner);
|
||||
$$ = (Node *) n;
|
||||
@ -4167,7 +4168,7 @@ ConstraintElem:
|
||||
NULL, yyscanner);
|
||||
$$ = (Node *) n;
|
||||
}
|
||||
| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
|
||||
| PRIMARY KEY '(' columnList opt_without_overlaps ')' opt_c_include opt_definition OptConsTableSpace
|
||||
ConstraintAttributeSpec
|
||||
{
|
||||
Constraint *n = makeNode(Constraint);
|
||||
@ -4175,11 +4176,12 @@ ConstraintElem:
|
||||
n->contype = CONSTR_PRIMARY;
|
||||
n->location = @1;
|
||||
n->keys = $4;
|
||||
n->including = $6;
|
||||
n->options = $7;
|
||||
n->without_overlaps = $5;
|
||||
n->including = $7;
|
||||
n->options = $8;
|
||||
n->indexname = NULL;
|
||||
n->indexspace = $8;
|
||||
processCASbits($9, @9, "PRIMARY KEY",
|
||||
n->indexspace = $9;
|
||||
processCASbits($10, @10, "PRIMARY KEY",
|
||||
&n->deferrable, &n->initdeferred, NULL,
|
||||
NULL, yyscanner);
|
||||
$$ = (Node *) n;
|
||||
@ -4247,6 +4249,11 @@ opt_no_inherit: NO INHERIT { $$ = true; }
|
||||
| /* EMPTY */ { $$ = false; }
|
||||
;
|
||||
|
||||
opt_without_overlaps:
|
||||
WITHOUT OVERLAPS { $$ = true; }
|
||||
| /*EMPTY*/ { $$ = false; }
|
||||
;
|
||||
|
||||
opt_column_list:
|
||||
'(' columnList ')' { $$ = $2; }
|
||||
| /*EMPTY*/ { $$ = NIL; }
|
||||
|
@ -1716,6 +1716,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
|
||||
index->unique = idxrec->indisunique;
|
||||
index->nulls_not_distinct = idxrec->indnullsnotdistinct;
|
||||
index->primary = idxrec->indisprimary;
|
||||
index->iswithoutoverlaps = (idxrec->indisprimary || idxrec->indisunique) && idxrec->indisexclusion;
|
||||
index->transformed = true; /* don't need transformIndexStmt */
|
||||
index->concurrent = false;
|
||||
index->if_not_exists = false;
|
||||
@ -1765,7 +1766,9 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
|
||||
int nElems;
|
||||
int i;
|
||||
|
||||
Assert(conrec->contype == CONSTRAINT_EXCLUSION);
|
||||
Assert(conrec->contype == CONSTRAINT_EXCLUSION ||
|
||||
(index->iswithoutoverlaps &&
|
||||
(conrec->contype == CONSTRAINT_PRIMARY || conrec->contype == CONSTRAINT_UNIQUE)));
|
||||
/* Extract operator OIDs from the pg_constraint tuple */
|
||||
datum = SysCacheGetAttrNotNull(CONSTROID, ht_constr,
|
||||
Anum_pg_constraint_conexclop);
|
||||
@ -2305,6 +2308,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
||||
}
|
||||
index->nulls_not_distinct = constraint->nulls_not_distinct;
|
||||
index->isconstraint = true;
|
||||
index->iswithoutoverlaps = constraint->without_overlaps;
|
||||
index->deferrable = constraint->deferrable;
|
||||
index->initdeferred = constraint->initdeferred;
|
||||
|
||||
@ -2397,6 +2401,11 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
||||
errmsg("index \"%s\" is not valid", index_name),
|
||||
parser_errposition(cxt->pstate, constraint->location)));
|
||||
|
||||
/*
|
||||
* Today we forbid non-unique indexes, but we could permit GiST
|
||||
* indexes whose last entry is a range type and use that to create a
|
||||
* WITHOUT OVERLAPS constraint (i.e. a temporal constraint).
|
||||
*/
|
||||
if (!index_form->indisunique)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
@ -2673,6 +2682,23 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
|
||||
notnullcmds = lappend(notnullcmds, notnullcmd);
|
||||
}
|
||||
}
|
||||
|
||||
if (constraint->without_overlaps)
|
||||
{
|
||||
/*
|
||||
* This enforces that there is at least one equality column
|
||||
* besides the WITHOUT OVERLAPS columns. This is per SQL
|
||||
* standard. XXX Do we need this?
|
||||
*/
|
||||
if (list_length(constraint->keys) < 2)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("constraint using WITHOUT OVERLAPS needs at least two columns"));
|
||||
|
||||
/* WITHOUT OVERLAPS requires a GiST index */
|
||||
index->accessMethod = "gist";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2380,6 +2380,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
|
||||
Anum_pg_constraint_conkey);
|
||||
|
||||
keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
|
||||
if (conForm->conwithoutoverlaps)
|
||||
appendStringInfoString(&buf, " WITHOUT OVERLAPS");
|
||||
|
||||
appendStringInfoChar(&buf, ')');
|
||||
|
||||
|
18
src/backend/utils/cache/relcache.c
vendored
18
src/backend/utils/cache/relcache.c
vendored
@ -5568,11 +5568,14 @@ RelationGetIdentityKeyBitmap(Relation relation)
|
||||
/*
|
||||
* RelationGetExclusionInfo -- get info about index's exclusion constraint
|
||||
*
|
||||
* This should be called only for an index that is known to have an
|
||||
* associated exclusion constraint. It returns arrays (palloc'd in caller's
|
||||
* context) of the exclusion operator OIDs, their underlying functions'
|
||||
* OIDs, and their strategy numbers in the index's opclasses. We cache
|
||||
* all this information since it requires a fair amount of work to get.
|
||||
* This should be called only for an index that is known to have an associated
|
||||
* exclusion constraint or primary key/unique constraint using WITHOUT
|
||||
* OVERLAPS.
|
||||
|
||||
* It returns arrays (palloc'd in caller's context) of the exclusion operator
|
||||
* OIDs, their underlying functions' OIDs, and their strategy numbers in the
|
||||
* index's opclasses. We cache all this information since it requires a fair
|
||||
* amount of work to get.
|
||||
*/
|
||||
void
|
||||
RelationGetExclusionInfo(Relation indexRelation,
|
||||
@ -5636,7 +5639,10 @@ RelationGetExclusionInfo(Relation indexRelation,
|
||||
int nelem;
|
||||
|
||||
/* We want the exclusion constraint owning the index */
|
||||
if (conform->contype != CONSTRAINT_EXCLUSION ||
|
||||
if ((conform->contype != CONSTRAINT_EXCLUSION &&
|
||||
!(conform->conwithoutoverlaps && (
|
||||
conform->contype == CONSTRAINT_PRIMARY
|
||||
|| conform->contype == CONSTRAINT_UNIQUE))) ||
|
||||
conform->conindid != RelationGetRelid(indexRelation))
|
||||
continue;
|
||||
|
||||
|
@ -7233,6 +7233,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
||||
i_conname,
|
||||
i_condeferrable,
|
||||
i_condeferred,
|
||||
i_conwithoutoverlaps,
|
||||
i_contableoid,
|
||||
i_conoid,
|
||||
i_condef,
|
||||
@ -7314,10 +7315,17 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
||||
|
||||
if (fout->remoteVersion >= 150000)
|
||||
appendPQExpBufferStr(query,
|
||||
"i.indnullsnotdistinct ");
|
||||
"i.indnullsnotdistinct, ");
|
||||
else
|
||||
appendPQExpBufferStr(query,
|
||||
"false AS indnullsnotdistinct ");
|
||||
"false AS indnullsnotdistinct, ");
|
||||
|
||||
if (fout->remoteVersion >= 170000)
|
||||
appendPQExpBufferStr(query,
|
||||
"c.conwithoutoverlaps ");
|
||||
else
|
||||
appendPQExpBufferStr(query,
|
||||
"NULL AS conwithoutoverlaps ");
|
||||
|
||||
/*
|
||||
* The point of the messy-looking outer join is to find a constraint that
|
||||
@ -7385,6 +7393,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
||||
i_conname = PQfnumber(res, "conname");
|
||||
i_condeferrable = PQfnumber(res, "condeferrable");
|
||||
i_condeferred = PQfnumber(res, "condeferred");
|
||||
i_conwithoutoverlaps = PQfnumber(res, "conwithoutoverlaps");
|
||||
i_contableoid = PQfnumber(res, "contableoid");
|
||||
i_conoid = PQfnumber(res, "conoid");
|
||||
i_condef = PQfnumber(res, "condef");
|
||||
@ -7492,6 +7501,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
|
||||
constrinfo->conindex = indxinfo[j].dobj.dumpId;
|
||||
constrinfo->condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
|
||||
constrinfo->condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
|
||||
constrinfo->conwithoutoverlaps = *(PQgetvalue(res, j, i_conwithoutoverlaps)) == 't';
|
||||
constrinfo->conislocal = true;
|
||||
constrinfo->separate = true;
|
||||
|
||||
@ -17129,6 +17139,8 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
|
||||
(k == 0) ? "" : ", ",
|
||||
fmtId(attname));
|
||||
}
|
||||
if (coninfo->conwithoutoverlaps)
|
||||
appendPQExpBufferStr(q, " WITHOUT OVERLAPS");
|
||||
|
||||
if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
|
||||
appendPQExpBufferStr(q, ") INCLUDE (");
|
||||
|
@ -479,6 +479,8 @@ typedef struct _constraintInfo
|
||||
DumpId conindex; /* identifies associated index if any */
|
||||
bool condeferrable; /* true if constraint is DEFERRABLE */
|
||||
bool condeferred; /* true if constraint is INITIALLY DEFERRED */
|
||||
bool conwithoutoverlaps; /* true if the constraint is WITHOUT
|
||||
* OVERLAPS */
|
||||
bool conislocal; /* true if constraint has local definition */
|
||||
bool separate; /* true if must dump as separate item */
|
||||
} ConstraintInfo;
|
||||
|
@ -1015,6 +1015,42 @@ my %tests = (
|
||||
},
|
||||
},
|
||||
|
||||
'CONSTRAINT PRIMARY KEY / WITHOUT OVERLAPS' => {
|
||||
create_sql => 'CREATE TABLE dump_test.test_table_tpk (
|
||||
col1 int4range,
|
||||
col2 tstzrange,
|
||||
CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS));',
|
||||
regexp => qr/^
|
||||
\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
|
||||
\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
|
||||
/xm,
|
||||
like => {
|
||||
%full_runs, %dump_test_schema_runs, section_post_data => 1,
|
||||
},
|
||||
unlike => {
|
||||
exclude_dump_test_schema => 1,
|
||||
only_dump_measurement => 1,
|
||||
},
|
||||
},
|
||||
|
||||
'CONSTRAINT UNIQUE / WITHOUT OVERLAPS' => {
|
||||
create_sql => 'CREATE TABLE dump_test.test_table_tuq (
|
||||
col1 int4range,
|
||||
col2 tstzrange,
|
||||
CONSTRAINT test_table_tuq_uq UNIQUE (col1, col2 WITHOUT OVERLAPS));',
|
||||
regexp => qr/^
|
||||
\QALTER TABLE ONLY dump_test.test_table_tuq\E \n^\s+
|
||||
\QADD CONSTRAINT test_table_tuq_uq UNIQUE (col1, col2 WITHOUT OVERLAPS);\E
|
||||
/xm,
|
||||
like => {
|
||||
%full_runs, %dump_test_schema_runs, section_post_data => 1,
|
||||
},
|
||||
unlike => {
|
||||
exclude_dump_test_schema => 1,
|
||||
only_dump_measurement => 1,
|
||||
},
|
||||
},
|
||||
|
||||
'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
|
||||
create_order => 4,
|
||||
create_sql => 'CREATE TABLE dump_test.test_table_fk (
|
||||
|
@ -2379,6 +2379,10 @@ describeOneTableDetails(const char *schemaname,
|
||||
else
|
||||
appendPQExpBufferStr(&buf, ", false AS indisreplident");
|
||||
appendPQExpBufferStr(&buf, ", c2.reltablespace");
|
||||
if (pset.sversion >= 170000)
|
||||
appendPQExpBufferStr(&buf, ", con.conwithoutoverlaps");
|
||||
else
|
||||
appendPQExpBufferStr(&buf, ", false AS conwithoutoverlaps");
|
||||
appendPQExpBuffer(&buf,
|
||||
"\nFROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n"
|
||||
" LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ('p','u','x'))\n"
|
||||
@ -2400,8 +2404,12 @@ describeOneTableDetails(const char *schemaname,
|
||||
printfPQExpBuffer(&buf, " \"%s\"",
|
||||
PQgetvalue(result, i, 0));
|
||||
|
||||
/* If exclusion constraint, print the constraintdef */
|
||||
if (strcmp(PQgetvalue(result, i, 7), "x") == 0)
|
||||
/*
|
||||
* If exclusion constraint or PK/UNIQUE constraint WITHOUT
|
||||
* OVERLAPS, print the constraintdef
|
||||
*/
|
||||
if (strcmp(PQgetvalue(result, i, 7), "x") == 0 ||
|
||||
strcmp(PQgetvalue(result, i, 12), "t") == 0)
|
||||
{
|
||||
appendPQExpBuffer(&buf, " %s",
|
||||
PQgetvalue(result, i, 6));
|
||||
|
@ -17,6 +17,7 @@
|
||||
#define GIST_H
|
||||
|
||||
#include "access/itup.h"
|
||||
#include "access/stratnum.h"
|
||||
#include "access/transam.h"
|
||||
#include "access/xlog.h"
|
||||
#include "access/xlogdefs.h"
|
||||
@ -246,4 +247,6 @@ typedef struct
|
||||
do { (e).key = (k); (e).rel = (r); (e).page = (pg); \
|
||||
(e).offset = (o); (e).leafkey = (l); } while (0)
|
||||
|
||||
extern StrategyNumber GistTranslateStratnum(Oid opclass, StrategyNumber strat);
|
||||
|
||||
#endif /* GIST_H */
|
||||
|
@ -57,6 +57,6 @@
|
||||
*/
|
||||
|
||||
/* yyyymmddN */
|
||||
#define CATALOG_VERSION_NO 202401201
|
||||
#define CATALOG_VERSION_NO 202401241
|
||||
|
||||
#endif
|
||||
|
@ -92,6 +92,7 @@ extern Oid index_create(Relation heapRelation,
|
||||
#define INDEX_CONSTR_CREATE_INIT_DEFERRED (1 << 2)
|
||||
#define INDEX_CONSTR_CREATE_UPDATE_INDEX (1 << 3)
|
||||
#define INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS (1 << 4)
|
||||
#define INDEX_CONSTR_CREATE_WITHOUT_OVERLAPS (1 << 5)
|
||||
|
||||
extern Oid index_concurrently_create_copy(Relation heapRelation,
|
||||
Oid oldIndexId,
|
||||
|
@ -107,6 +107,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
|
||||
/* Has a local definition and cannot be inherited */
|
||||
bool connoinherit;
|
||||
|
||||
/*
|
||||
* For primary keys and unique constraints, signifies the last column uses
|
||||
* overlaps instead of equals.
|
||||
*/
|
||||
bool conwithoutoverlaps;
|
||||
|
||||
#ifdef CATALOG_VARLEN /* variable-length fields start here */
|
||||
|
||||
/*
|
||||
@ -146,7 +152,8 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
|
||||
|
||||
/*
|
||||
* If an exclusion constraint, the OIDs of the exclusion operators for
|
||||
* each column of the constraint
|
||||
* each column of the constraint. Also set for unique constraints/primary
|
||||
* keys using WITHOUT OVERLAPS.
|
||||
*/
|
||||
Oid conexclop[1] BKI_LOOKUP(pg_operator);
|
||||
|
||||
@ -238,6 +245,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
|
||||
bool conIsLocal,
|
||||
int conInhCount,
|
||||
bool conNoInherit,
|
||||
bool conWithoutOverlaps,
|
||||
bool is_internal);
|
||||
|
||||
extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#ifndef DEFREM_H
|
||||
#define DEFREM_H
|
||||
|
||||
#include "access/stratnum.h"
|
||||
#include "catalog/objectaddress.h"
|
||||
#include "nodes/params.h"
|
||||
#include "parser/parse_node.h"
|
||||
@ -44,10 +45,13 @@ extern char *ChooseRelationName(const char *name1, const char *name2,
|
||||
extern bool CheckIndexCompatible(Oid oldId,
|
||||
const char *accessMethodName,
|
||||
const List *attributeList,
|
||||
const List *exclusionOpNames);
|
||||
const List *exclusionOpNames,
|
||||
bool isWithoutOverlaps);
|
||||
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
|
||||
extern Oid ResolveOpClass(const List *opclass, Oid attrType,
|
||||
const char *accessMethodName, Oid accessMethodId);
|
||||
extern void GetOperatorFromWellKnownStrategy(Oid opclass, Oid atttype,
|
||||
Oid *opid, StrategyNumber *strat);
|
||||
|
||||
/* commands/functioncmds.c */
|
||||
extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt);
|
||||
|
@ -2590,6 +2590,7 @@ typedef struct Constraint
|
||||
bool nulls_not_distinct; /* null treatment for UNIQUE constraints */
|
||||
List *keys; /* String nodes naming referenced key
|
||||
* column(s); also used for NOT NULL */
|
||||
bool without_overlaps; /* WITHOUT OVERLAPS specified */
|
||||
List *including; /* String nodes naming referenced nonkey
|
||||
* column(s) */
|
||||
|
||||
@ -3219,6 +3220,7 @@ typedef struct IndexStmt
|
||||
bool nulls_not_distinct; /* null treatment for UNIQUE constraints */
|
||||
bool primary; /* is index a primary key? */
|
||||
bool isconstraint; /* is it for a pkey/unique constraint? */
|
||||
bool iswithoutoverlaps; /* is the constraint WITHOUT OVERLAPS? */
|
||||
bool deferrable; /* is the constraint DEFERRABLE? */
|
||||
bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
|
||||
bool transformed; /* true when transformIndexStmt is finished */
|
||||
|
395
src/test/regress/expected/without_overlaps.out
Normal file
395
src/test/regress/expected/without_overlaps.out
Normal file
@ -0,0 +1,395 @@
|
||||
-- Tests for WITHOUT OVERLAPS.
|
||||
--
|
||||
-- We leave behind several tables to test pg_dump etc:
|
||||
-- temporal_rng, temporal_rng2,
|
||||
-- temporal_fk_rng2rng.
|
||||
--
|
||||
-- test input parser
|
||||
--
|
||||
-- PK with no columns just WITHOUT OVERLAPS:
|
||||
CREATE TABLE temporal_rng (
|
||||
valid_at tsrange,
|
||||
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 tsrange,
|
||||
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 | tsrange | | 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 tsrange,
|
||||
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 | tsrange | | 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 tsrange,
|
||||
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 tsrange,
|
||||
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 | tsrange | | |
|
||||
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 tsrange,
|
||||
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 | tsrange | | |
|
||||
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 tsrange
|
||||
);
|
||||
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 tsrange
|
||||
);
|
||||
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 tsrange
|
||||
);
|
||||
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 tsrange
|
||||
);
|
||||
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 tsrange,
|
||||
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 tsrange,
|
||||
ADD CONSTRAINT temporal3_uq
|
||||
UNIQUE (id, valid_at WITHOUT OVERLAPS);
|
||||
DROP TABLE temporal3;
|
||||
--
|
||||
-- test PK inserts
|
||||
--
|
||||
-- okay:
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
|
||||
INSERT INTO temporal_rng VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
|
||||
INSERT INTO temporal_rng VALUES ('[3,3]', tsrange('2018-01-01', NULL));
|
||||
-- should fail:
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
|
||||
ERROR: conflicting key value violates exclusion constraint "temporal_rng_pk"
|
||||
DETAIL: Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
|
||||
INSERT INTO temporal_rng VALUES (NULL, tsrange('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, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
|
||||
INSERT INTO temporal_rng VALUES ('[3,3]', 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,1]', daterange('2000-01-01', '2010-01-01'), '[7,7]', 'foo'),
|
||||
('[2,2]', daterange('2000-01-01', '2010-01-01'), '[9,9]', 'bar')
|
||||
;
|
||||
DROP TABLE temporal3;
|
||||
--
|
||||
-- test changing the PK's dependencies
|
||||
--
|
||||
CREATE TABLE temporal3 (
|
||||
id int4range,
|
||||
valid_at tsrange,
|
||||
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,1]', '[2,2]');
|
||||
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,3]', '[4,4]');
|
||||
INSERT INTO temporal_partitioned VALUES
|
||||
('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
|
||||
('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
|
||||
('[3,3]', daterange('2000-01-01', '2010-01-01'), 'three');
|
||||
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+-------
|
||||
[1,2) | [01-01-2000,02-01-2000) | one
|
||||
[1,2) | [02-01-2000,03-01-2000) | one
|
||||
[3,4) | [01-01-2000,01-01-2010) | three
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM tp1 ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+------
|
||||
[1,2) | [01-01-2000,02-01-2000) | one
|
||||
[1,2) | [02-01-2000,03-01-2000) | one
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM tp2 ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+-------
|
||||
[3,4) | [01-01-2000,01-01-2010) | 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,1]', '[2,2]');
|
||||
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,3]', '[4,4]');
|
||||
INSERT INTO temporal_partitioned VALUES
|
||||
('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
|
||||
('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
|
||||
('[3,3]', daterange('2000-01-01', '2010-01-01'), 'three');
|
||||
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+-------
|
||||
[1,2) | [01-01-2000,02-01-2000) | one
|
||||
[1,2) | [02-01-2000,03-01-2000) | one
|
||||
[3,4) | [01-01-2000,01-01-2010) | three
|
||||
(3 rows)
|
||||
|
||||
SELECT * FROM tp1 ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+------
|
||||
[1,2) | [01-01-2000,02-01-2000) | one
|
||||
[1,2) | [02-01-2000,03-01-2000) | one
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM tp2 ORDER BY id, valid_at;
|
||||
id | valid_at | name
|
||||
-------+-------------------------+-------
|
||||
[3,4) | [01-01-2000,01-01-2010) | three
|
||||
(1 row)
|
||||
|
||||
DROP TABLE temporal_partitioned;
|
@ -78,7 +78,7 @@ test: brin_bloom brin_multi
|
||||
# psql depends on create_am
|
||||
# amutils depends on geometry, create_index_spgist, hash_index, brin
|
||||
# ----------
|
||||
test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
|
||||
test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role without_overlaps
|
||||
|
||||
# collate.*.utf8 tests cannot be run in parallel with each other
|
||||
test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 collate.windows.win1252
|
||||
|
290
src/test/regress/sql/without_overlaps.sql
Normal file
290
src/test/regress/sql/without_overlaps.sql
Normal file
@ -0,0 +1,290 @@
|
||||
-- Tests for WITHOUT OVERLAPS.
|
||||
--
|
||||
-- We leave behind several tables to test pg_dump etc:
|
||||
-- temporal_rng, temporal_rng2,
|
||||
-- temporal_fk_rng2rng.
|
||||
|
||||
--
|
||||
-- test input parser
|
||||
--
|
||||
|
||||
-- PK with no columns just WITHOUT OVERLAPS:
|
||||
|
||||
CREATE TABLE temporal_rng (
|
||||
valid_at tsrange,
|
||||
CONSTRAINT temporal_rng_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
-- 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 tsrange,
|
||||
CONSTRAINT temporal_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
\d temporal_rng
|
||||
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
|
||||
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng_pk';
|
||||
|
||||
-- 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 tsrange,
|
||||
CONSTRAINT temporal_rng2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
\d temporal_rng2
|
||||
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng2_pk';
|
||||
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng2_pk';
|
||||
|
||||
|
||||
-- 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
|
||||
|
||||
-- UNIQUE with no columns just WITHOUT OVERLAPS:
|
||||
|
||||
CREATE TABLE temporal_rng3 (
|
||||
valid_at tsrange,
|
||||
CONSTRAINT temporal_rng3_uq UNIQUE (valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
-- UNIQUE with one column plus a range:
|
||||
|
||||
CREATE TABLE temporal_rng3 (
|
||||
id int4range,
|
||||
valid_at tsrange,
|
||||
CONSTRAINT temporal_rng3_uq UNIQUE (id, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
\d temporal_rng3
|
||||
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
|
||||
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
|
||||
DROP TABLE temporal_rng3;
|
||||
|
||||
-- UNIQUE with two columns plus a range:
|
||||
CREATE TABLE temporal_rng3 (
|
||||
id1 int4range,
|
||||
id2 int4range,
|
||||
valid_at tsrange,
|
||||
CONSTRAINT temporal_rng3_uq UNIQUE (id1, id2, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
\d temporal_rng3
|
||||
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
|
||||
SELECT pg_get_indexdef(conindid, 0, true) FROM pg_constraint WHERE conname = 'temporal_rng3_uq';
|
||||
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 tsrange
|
||||
);
|
||||
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 tsrange
|
||||
);
|
||||
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;
|
||||
DROP TABLE temporal3;
|
||||
|
||||
-- UNIQUE with USING INDEX (not possible):
|
||||
CREATE TABLE temporal3 (
|
||||
id int4range,
|
||||
valid_at tsrange
|
||||
);
|
||||
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;
|
||||
DROP TABLE temporal3;
|
||||
|
||||
-- UNIQUE with USING [UNIQUE] INDEX (possible but not a temporal constraint):
|
||||
CREATE TABLE temporal3 (
|
||||
id int4range,
|
||||
valid_at tsrange
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_temporal3_uq ON temporal3 (id, valid_at);
|
||||
ALTER TABLE temporal3
|
||||
ADD CONSTRAINT temporal3_uq
|
||||
UNIQUE USING INDEX idx_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 tsrange,
|
||||
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 tsrange,
|
||||
ADD CONSTRAINT temporal3_uq
|
||||
UNIQUE (id, valid_at WITHOUT OVERLAPS);
|
||||
DROP TABLE temporal3;
|
||||
|
||||
--
|
||||
-- test PK inserts
|
||||
--
|
||||
|
||||
-- okay:
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
|
||||
INSERT INTO temporal_rng VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
|
||||
INSERT INTO temporal_rng VALUES ('[3,3]', tsrange('2018-01-01', NULL));
|
||||
|
||||
-- should fail:
|
||||
INSERT INTO temporal_rng VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
|
||||
INSERT INTO temporal_rng VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
|
||||
INSERT INTO temporal_rng VALUES ('[3,3]', 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,1]', daterange('2000-01-01', '2010-01-01'), '[7,7]', 'foo'),
|
||||
('[2,2]', daterange('2000-01-01', '2010-01-01'), '[9,9]', 'bar')
|
||||
;
|
||||
DROP TABLE temporal3;
|
||||
|
||||
--
|
||||
-- test changing the PK's dependencies
|
||||
--
|
||||
|
||||
CREATE TABLE temporal3 (
|
||||
id int4range,
|
||||
valid_at tsrange,
|
||||
CONSTRAINT temporal3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
|
||||
);
|
||||
|
||||
ALTER TABLE temporal3 ALTER COLUMN valid_at DROP NOT NULL;
|
||||
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,1]', '[2,2]');
|
||||
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,3]', '[4,4]');
|
||||
INSERT INTO temporal_partitioned VALUES
|
||||
('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
|
||||
('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
|
||||
('[3,3]', daterange('2000-01-01', '2010-01-01'), 'three');
|
||||
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
|
||||
SELECT * FROM tp1 ORDER BY id, valid_at;
|
||||
SELECT * FROM tp2 ORDER BY id, valid_at;
|
||||
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,1]', '[2,2]');
|
||||
CREATE TABLE tp2 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,3]', '[4,4]');
|
||||
INSERT INTO temporal_partitioned VALUES
|
||||
('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
|
||||
('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
|
||||
('[3,3]', daterange('2000-01-01', '2010-01-01'), 'three');
|
||||
SELECT * FROM temporal_partitioned ORDER BY id, valid_at;
|
||||
SELECT * FROM tp1 ORDER BY id, valid_at;
|
||||
SELECT * FROM tp2 ORDER BY id, valid_at;
|
||||
DROP TABLE temporal_partitioned;
|
Loading…
x
Reference in New Issue
Block a user