1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-22 02:52:08 +03: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:
Peter Eisentraut
2024-01-24 15:43:41 +01:00
parent 74a7306310
commit 46a0cd4cef
34 changed files with 1135 additions and 52 deletions

View File

@ -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()
*