Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 76ddc88

Browse files
committed
Check equality semantics for unique indexes on partitioned tables.
We require the partition key to be a subset of the set of columns being made unique, so that physically-separate indexes on the different partitions are sufficient to enforce the uniqueness constraint. The existing code checked that the listed columns appear, but did not inquire into the index semantics, which is a serious oversight given that different index opclasses might enforce completely different notions of uniqueness. Ideally, perhaps, we'd just match the partition key opfamily to the index opfamily. But hash partitioning uses hash opfamilies which we can't directly match to btree opfamilies. Hence, look up the equality operator in each family, and accept if it's the same operator. This should be okay in a fairly general sense, since the equality operator ought to precisely represent the opfamily's notion of uniqueness. A remaining weak spot is that we don't have a cross-index-AM notion of which opfamily member is "equality". But we know which one to use for hash and btree AMs, and those are the only two that are relevant here at present. (Any non-core AMs that know how to enforce equality are out of luck, for now.) Back-patch to v11 where this feature was introduced. Guancheng Luo, revised a bit by me Discussion: https://postgr.es/m/[email protected]
1 parent 7054e12 commit 76ddc88

File tree

1 file changed

+79
-26
lines changed

1 file changed

+79
-26
lines changed

src/backend/commands/indexcmds.c

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "postgres.h"
1717

1818
#include "access/amapi.h"
19+
#include "access/hash.h"
1920
#include "access/htup_details.h"
2021
#include "access/reloptions.h"
2122
#include "access/sysattr.h"
@@ -691,44 +692,75 @@ DefineIndex(Oid relationId,
691692

692693
/*
693694
* If this table is partitioned and we're creating a unique index or a
694-
* primary key, make sure that the indexed columns are part of the
695-
* partition key. Otherwise it would be possible to violate uniqueness by
696-
* putting values that ought to be unique in different partitions.
695+
* primary key, make sure that the partition key is a subset of the
696+
* index's columns. Otherwise it would be possible to violate uniqueness
697+
* by putting values that ought to be unique in different partitions.
697698
*
698699
* We could lift this limitation if we had global indexes, but those have
699700
* their own problems, so this is a useful feature combination.
700701
*/
701702
if (partitioned && (stmt->unique || stmt->primary))
702703
{
703-
PartitionKey key = rel->rd_partkey;
704+
PartitionKey key = RelationGetPartitionKey(rel);
705+
const char *constraint_type;
704706
int i;
705707

708+
if (stmt->primary)
709+
constraint_type = "PRIMARY KEY";
710+
else if (stmt->unique)
711+
constraint_type = "UNIQUE";
712+
else if (stmt->excludeOpNames != NIL)
713+
constraint_type = "EXCLUDE";
714+
else
715+
{
716+
elog(ERROR, "unknown constraint type");
717+
constraint_type = NULL; /* keep compiler quiet */
718+
}
719+
706720
/*
707-
* A partitioned table can have unique indexes, as long as all the
708-
* columns in the partition key appear in the unique key. A
709-
* partition-local index can enforce global uniqueness iff the PK
710-
* value completely determines the partition that a row is in.
711-
*
712-
* Thus, verify that all the columns in the partition key appear in
713-
* the unique key definition.
721+
* Verify that all the columns in the partition key appear in the
722+
* unique key definition, with the same notion of equality.
714723
*/
715724
for (i = 0; i < key->partnatts; i++)
716725
{
717726
bool found = false;
727+
int eq_strategy;
728+
Oid ptkey_eqop;
718729
int j;
719-
const char *constraint_type;
720-
721-
if (stmt->primary)
722-
constraint_type = "PRIMARY KEY";
723-
else if (stmt->unique)
724-
constraint_type = "UNIQUE";
725-
else if (stmt->excludeOpNames != NIL)
726-
constraint_type = "EXCLUDE";
730+
731+
/*
732+
* Identify the equality operator associated with this partkey
733+
* column. For list and range partitioning, partkeys use btree
734+
* operator classes; hash partitioning uses hash operator classes.
735+
* (Keep this in sync with ComputePartitionAttrs!)
736+
*/
737+
if (key->strategy == PARTITION_STRATEGY_HASH)
738+
eq_strategy = HTEqualStrategyNumber;
727739
else
728-
{
729-
elog(ERROR, "unknown constraint type");
730-
constraint_type = NULL; /* keep compiler quiet */
731-
}
740+
eq_strategy = BTEqualStrategyNumber;
741+
742+
ptkey_eqop = get_opfamily_member(key->partopfamily[i],
743+
key->partopcintype[i],
744+
key->partopcintype[i],
745+
eq_strategy);
746+
if (!OidIsValid(ptkey_eqop))
747+
elog(ERROR, "missing operator %d(%u,%u) in partition opfamily %u",
748+
eq_strategy, key->partopcintype[i], key->partopcintype[i],
749+
key->partopfamily[i]);
750+
751+
/*
752+
* We'll need to be able to identify the equality operators
753+
* associated with index columns, too. We know what to do with
754+
* btree opclasses; if there are ever any other index types that
755+
* support unique indexes, this logic will need extension.
756+
*/
757+
if (accessMethodId == BTREE_AM_OID)
758+
eq_strategy = BTEqualStrategyNumber;
759+
else
760+
ereport(ERROR,
761+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
762+
errmsg("cannot match partition key to an index using access method \"%s\"",
763+
accessMethodName)));
732764

733765
/*
734766
* It may be possible to support UNIQUE constraints when partition
@@ -742,19 +774,40 @@ DefineIndex(Oid relationId,
742774
errdetail("%s constraints cannot be used when partition keys include expressions.",
743775
constraint_type)));
744776

777+
/* Search the index column(s) for a match */
745778
for (j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
746779
{
747780
if (key->partattrs[i] == indexInfo->ii_IndexAttrNumbers[j])
748781
{
749-
found = true;
750-
break;
782+
/* Matched the column, now what about the equality op? */
783+
Oid idx_opfamily;
784+
Oid idx_opcintype;
785+
786+
idx_opfamily = get_opclass_family(classObjectId[j]);
787+
idx_opcintype = get_opclass_input_type(classObjectId[j]);
788+
if (OidIsValid(idx_opfamily) && OidIsValid(idx_opcintype))
789+
{
790+
Oid idx_eqop;
791+
792+
idx_eqop = get_opfamily_member(idx_opfamily,
793+
idx_opcintype,
794+
idx_opcintype,
795+
eq_strategy);
796+
if (ptkey_eqop == idx_eqop)
797+
{
798+
found = true;
799+
break;
800+
}
801+
}
751802
}
752803
}
804+
753805
if (!found)
754806
{
755807
Form_pg_attribute att;
756808

757-
att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
809+
att = TupleDescAttr(RelationGetDescr(rel),
810+
key->partattrs[i] - 1);
758811
ereport(ERROR,
759812
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
760813
errmsg("insufficient columns in %s constraint definition",

0 commit comments

Comments
 (0)