mirror of
https://github.com/HypoPG/hypopg
synced 2026-05-23 17:18:44 +00:00
Check for (hypo) constraint compatibility with (hypo) partitioning
This commit is contained in:
parent
f472fb12aa
commit
75c8987e2b
4 changed files with 373 additions and 0 deletions
|
|
@ -223,3 +223,55 @@ SELECT COUNT(*) AS nb FROM hypopg_create_index('CREATE INDEX ON hypo_part_multi_
|
|||
ERROR: hypopg: cannot add hypothetical index on non-leaf or non-root hypothetical partition
|
||||
SELECT COUNT(*) AS nb FROM hypopg_create_index('CREATE INDEX ON hypo_part_multi_1_q1 (dpt)');
|
||||
ERROR: hypopg: cannot add hypothetical index on non-leaf or non-root hypothetical partition
|
||||
-- pk constraint check
|
||||
CREATE TABLE t_pk(id integer primary key, val text) PARTITION BY LIST (val);
|
||||
ERROR: insufficient columns in PRIMARY KEY constraint definition
|
||||
DETAIL: PRIMARY KEY constraint on table "t_pk" lacks column "val" which is part of the partition key.
|
||||
CREATE TABLE hypo_t_pk(id integer primary key, val text);
|
||||
SELECT hypopg_partition_table('hypo_t_pk', 'PARTITION BY LIST (val)');
|
||||
ERROR: hypopg: insufficient columns in unique constraint definition
|
||||
DETAIL: unique constraint on table "hypo_t_pk" lacks column "val" which is part of the hypothetical partition key.
|
||||
DROP TABLE hypo_t_pk;
|
||||
-- unique constraint check
|
||||
CREATE TABLE t_unique(id integer, val text) PARTITION BY LIST (val);
|
||||
CREATE UNIQUE INDEX ON t_unique(id);
|
||||
ERROR: insufficient columns in UNIQUE constraint definition
|
||||
DETAIL: UNIQUE constraint on table "t_unique" lacks column "val" which is part of the partition key.
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
CREATE UNIQUE INDEX ON hypo_t_unique(id);
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
ERROR: hypopg: insufficient columns in unique constraint definition
|
||||
DETAIL: unique constraint on table "hypo_t_unique" lacks column "val" which is part of the hypothetical partition key.
|
||||
DROP TABLE hypo_t_unique;
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
SELECT count(*) FROM hypopg_create_index('CREATE UNIQUE INDEX on hypo_t_unique (id)');
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
ERROR: hypopg: insufficient columns in unique hypothetical constraint definition
|
||||
DETAIL: unique constraint on table "hypo_t_unique" lacks column "val" which is part of the hypothetical partition key.
|
||||
DROP TABLE hypo_t_unique;
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
hypopg_partition_table
|
||||
------------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT count(*) FROM hypopg_create_index('CREATE UNIQUE INDEX on hypo_t_unique (id)');
|
||||
ERROR: hypopg: insufficient columns in unique hypothetical constraint definition
|
||||
DETAIL: unique hypothetical constraint on table "hypo_t_unique" lacks column "val" which is part of the hypothetical partition key.
|
||||
DROP TABLE t_unique;
|
||||
DROP TABLE hypo_t_unique;
|
||||
-- constraint exclusion check
|
||||
CREATE TABLE t_constrext(c circle, val text, EXCLUDE USING gist(c WITH &&)) PARTITION BY LIST (val);
|
||||
ERROR: exclusion constraints are not supported on partitioned tables
|
||||
LINE 1: CREATE TABLE t_constrext(c circle, val text, EXCLUDE USING g...
|
||||
^
|
||||
CREATE TABLE hypo_t_constrext(c circle, val text, EXCLUDE USING gist(c WITH &&));
|
||||
SELECT hypopg_partition_table('hypo_t_constrext', 'PARTITION BY LIST (val)');
|
||||
ERROR: exclusion constraints are not supported on hypothetically partitioned tables
|
||||
DROP TABLE hypo_t_constrext;
|
||||
|
|
|
|||
115
hypopg_index.c
115
hypopg_index.c
|
|
@ -57,6 +57,9 @@
|
|||
#include "utils/builtins.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/rel.h"
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
#include "utils/partcache.h"
|
||||
#endif
|
||||
#if PG_VERSION_NUM >= 90500
|
||||
#include "utils/ruleutils.h"
|
||||
#endif
|
||||
|
|
@ -98,6 +101,8 @@ static void hypo_estimate_index_simple(hypoIndex *entry,
|
|||
static void hypo_estimate_index(hypoIndex *entry, RelOptInfo *rel,
|
||||
PlannerInfo *root);
|
||||
static int hypo_estimate_index_colsize(hypoIndex *entry, int col);
|
||||
static void hypo_index_check_uniqueness_compatibility(IndexStmt *stmt,
|
||||
Oid relid, hypoIndex *entry);
|
||||
static void hypo_index_pfree(hypoIndex *entry);
|
||||
static bool hypo_index_remove(Oid indexid);
|
||||
static const hypoIndex *hypo_index_store_parsetree(IndexStmt *node,
|
||||
|
|
@ -399,6 +404,7 @@ hypo_index_store_parsetree(IndexStmt *node, const char *queryString)
|
|||
node = transformIndexStmt(relid, node, queryString);
|
||||
|
||||
nkeycolumns = list_length(node->indexParams);
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
if (list_intersection(node->indexParams, node->indexIncludingParams) != NIL)
|
||||
ereport(ERROR,
|
||||
|
|
@ -689,6 +695,14 @@ hypo_index_store_parsetree(IndexStmt *node, const char *queryString)
|
|||
errmsg("hypopg: index creation on system columns is not supported")));
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
/*
|
||||
* check for uniqueness compatibility with (hypothetically) partitioned
|
||||
* tables
|
||||
*/
|
||||
hypo_index_check_uniqueness_compatibility(node, relid, entry);
|
||||
#endif
|
||||
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
attn = nkeycolumns;
|
||||
|
|
@ -907,6 +921,107 @@ hypo_index_remove(Oid indexid)
|
|||
return false;
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
/*
|
||||
* If this table is partitioned and we're creating a unique index or a
|
||||
* primary key, make sure that the indexed columns are part of the
|
||||
* partition key. Otherwise it would be possible to violate uniqueness by
|
||||
* putting values that ought to be unique in different partitions.
|
||||
*
|
||||
* We could lift this limitation if we had global indexes, but those have
|
||||
* their own problems, so this is a useful feature combination.
|
||||
*
|
||||
* Heavily inspired on DefineIndex.
|
||||
*/
|
||||
static void
|
||||
hypo_index_check_uniqueness_compatibility(IndexStmt *stmt, Oid relid,
|
||||
hypoIndex *entry)
|
||||
{
|
||||
PartitionKey key = NULL;
|
||||
Relation rel;
|
||||
const char *extra;
|
||||
int i;
|
||||
|
||||
if (!stmt->unique)
|
||||
return;
|
||||
|
||||
rel = relation_open(relid, AccessShareLock);
|
||||
|
||||
if (hypo_table_oid_is_hypothetical(relid))
|
||||
{
|
||||
hypoTable *table = hypo_find_table(relid, false);
|
||||
|
||||
key = table->partkey;
|
||||
extra = "hypothetical ";
|
||||
}
|
||||
else
|
||||
{
|
||||
key = rel->rd_partkey;
|
||||
extra = "";
|
||||
}
|
||||
|
||||
if (!key)
|
||||
{
|
||||
relation_close(rel, AccessShareLock);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* A partitioned table can have unique indexes, as long as all the
|
||||
* columns in the partition key appear in the unique key. A
|
||||
* partition-local index can enforce global uniqueness iff the PK
|
||||
* value completely determines the partition that a row is in.
|
||||
*
|
||||
* Thus, verify that all the columns in the partition key appear in
|
||||
* the unique key definition.
|
||||
*/
|
||||
for (i = 0; i < key->partnatts; i++)
|
||||
{
|
||||
bool found = false;
|
||||
int j;
|
||||
const char *constraint_type = "unique";
|
||||
|
||||
/*
|
||||
* It may be possible to support UNIQUE constraints when partition
|
||||
* keys are expressions, but is it worth it? Give up for now.
|
||||
*/
|
||||
if (key->partattrs[i] == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: unsupported %s hypothetical constraint with %spartition key definition",
|
||||
constraint_type, extra),
|
||||
errdetail("%s hypothetical constraints cannot be used when %spartition keys include expressions.",
|
||||
constraint_type, extra)));
|
||||
|
||||
for (j = 0; j < entry->nkeycolumns; j++)
|
||||
{
|
||||
if (key->partattrs[i] == entry->indexkeys[j])
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
Form_pg_attribute att;
|
||||
|
||||
att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: insufficient columns in %s hypothetical constraint definition",
|
||||
constraint_type),
|
||||
errdetail("%s hypothetical constraint on table \"%s\" lacks column \"%s\" which is part of the %spartition key.",
|
||||
constraint_type, RelationGetRelationName(rel),
|
||||
NameStr(att->attname),
|
||||
extra)));
|
||||
}
|
||||
}
|
||||
|
||||
relation_close(rel, AccessShareLock);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
/* pfree all allocated memory for within an hypoIndex and the entry itself. */
|
||||
static void
|
||||
hypo_index_pfree(hypoIndex *entry)
|
||||
|
|
|
|||
170
hypopg_table.c
170
hypopg_table.c
|
|
@ -25,6 +25,7 @@
|
|||
#include "access/hash.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/nbtree.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/partition.h"
|
||||
#include "catalog/pg_class.h"
|
||||
|
|
@ -63,6 +64,7 @@
|
|||
|
||||
#include "include/hypopg.h"
|
||||
#include "include/hypopg_analyze.h"
|
||||
#include "include/hypopg_index.h"
|
||||
#include "include/hypopg_table.h"
|
||||
|
||||
/*--- Variables exported ---*/
|
||||
|
|
@ -113,6 +115,9 @@ static Oid hypo_get_default_partition_oid(hypoTable *parent);
|
|||
static char *hypo_get_partbounddef(hypoTable *entry);
|
||||
static char *hypo_get_partkeydef(hypoTable *entry);
|
||||
static hypoTable *hypo_newTable(Oid parentid);
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
static void hypo_table_check_constraints_compatibility(hypoTable *table);
|
||||
#endif
|
||||
static hypoTable *hypo_table_find_parent_oid(Oid parentid);
|
||||
static void hypo_table_pfree(hypoTable *entry, bool freeFieldsOnly);
|
||||
static hypoTable *hypo_table_store_parsetree(CreateStmt *node,
|
||||
|
|
@ -1654,6 +1659,162 @@ hypo_get_partkeydef(hypoTable *entry)
|
|||
return buf.data;
|
||||
}
|
||||
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
/*
|
||||
* check if the wanted PARTITION BY clause is compatible with any exiting
|
||||
* unique/pk index.
|
||||
*
|
||||
* Heavily inspired on get_relation_info and DefineIndex
|
||||
*/
|
||||
static void
|
||||
hypo_table_check_constraints_compatibility(hypoTable *table)
|
||||
{
|
||||
Relation rel = heap_open(table->rootid, AccessShareLock);
|
||||
List *idxlist = RelationGetIndexList(rel);
|
||||
PartitionKey key = table->partkey;
|
||||
ListCell *lc;
|
||||
|
||||
/* The partey should have already been generated */
|
||||
Assert(key);
|
||||
|
||||
/* adapted from DefineIndex */
|
||||
foreach(lc, idxlist)
|
||||
{
|
||||
Relation idxRel = index_open(lfirst_oid(lc), AccessShareLock);
|
||||
IndexInfo *indexInfo = BuildIndexInfo(idxRel);
|
||||
Form_pg_index index;
|
||||
int i;
|
||||
|
||||
index = idxRel->rd_index;
|
||||
|
||||
if (!IndexIsValid(index))
|
||||
{
|
||||
index_close(idxRel, AccessShareLock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index->indisexclusion)
|
||||
{
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("exclusion constraints are not supported on hypothetically partitioned tables")));
|
||||
}
|
||||
|
||||
if (!index->indisunique)
|
||||
{
|
||||
index_close(idxRel, AccessShareLock);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* A partitioned table can have unique indexes, as long as all the
|
||||
* columns in the partition key appear in the unique key. A
|
||||
* partition-local index can enforce global uniqueness iff the PK
|
||||
* value completely determines the partition that a row is in.
|
||||
*
|
||||
* Thus, verify that all the columns in the partition key appear in
|
||||
* the unique key definition.
|
||||
*/
|
||||
for (i = 0; i < key->partnatts; i++)
|
||||
{
|
||||
bool found = false;
|
||||
int j;
|
||||
/* FIXME detect the real constraint type */
|
||||
const char *constraint_type = "unique";
|
||||
|
||||
/*
|
||||
* It may be possible to support UNIQUE constraints when partition
|
||||
* keys are expressions, but is it worth it? Give up for now.
|
||||
*/
|
||||
if (key->partattrs[i] == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: unsupported %s constraint with hypothetical partition key definition",
|
||||
constraint_type),
|
||||
errdetail("%s constraints cannot be used when hypothetical partition keys include expressions.",
|
||||
constraint_type)));
|
||||
|
||||
for (j = 0; j < indexInfo->ii_NumIndexAttrs; j++)
|
||||
{
|
||||
if (key->partattrs[i] == indexInfo->ii_IndexAttrNumbers[j])
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
Form_pg_attribute att;
|
||||
|
||||
att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: insufficient columns in %s constraint definition",
|
||||
constraint_type),
|
||||
errdetail("%s constraint on table \"%s\" lacks column \"%s\" which is part of the hypothetical partition key.",
|
||||
constraint_type, RelationGetRelationName(rel),
|
||||
NameStr(att->attname))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
index_close(idxRel, AccessShareLock);
|
||||
}
|
||||
|
||||
/* same, but for hypothetical indexes */
|
||||
foreach(lc, hypoIndexes)
|
||||
{
|
||||
hypoIndex *idx = (hypoIndex *) lfirst(lc);
|
||||
int i;
|
||||
|
||||
if ((idx->relid != table->rootid && idx->relid != table->oid) ||
|
||||
!idx->unique)
|
||||
continue;
|
||||
|
||||
for (i = 0; i < key->partnatts; i++)
|
||||
{
|
||||
bool found = false;
|
||||
int j;
|
||||
/* FIXME detect the real constraint type */
|
||||
const char *constraint_type = "unique";
|
||||
|
||||
if (key->partattrs[i] == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: unsupported hypothetical %s constraint with hypothetical partition key definition",
|
||||
constraint_type),
|
||||
errdetail("hypothetical %s constraints cannot be used when hypothetical partition keys include expressions.",
|
||||
constraint_type)));
|
||||
|
||||
for (j = 0; j < idx->nkeycolumns; j++)
|
||||
{
|
||||
if (key->partattrs[i] == idx->indexkeys[j])
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
Form_pg_attribute att;
|
||||
|
||||
att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("hypopg: insufficient columns in %s hypothetical constraint definition",
|
||||
constraint_type),
|
||||
errdetail("%s constraint on table \"%s\" lacks column \"%s\" which is part of the hypothetical partition key.",
|
||||
constraint_type, RelationGetRelationName(rel),
|
||||
NameStr(att->attname))));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
heap_close(rel, AccessShareLock);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* palloc a new hypoTable, and give it a new OID, and some other global stuff.
|
||||
*/
|
||||
|
|
@ -1959,7 +2120,16 @@ hypo_table_store_parsetree(CreateStmt *node, const char *queryString,
|
|||
|
||||
/* The CreateStmt specified a PARTITION BY clause, store it */
|
||||
if (stmt->partspec)
|
||||
{
|
||||
hypo_generate_partkey(stmt, rootid, entry);
|
||||
#if PG_VERSION_NUM >= 110000
|
||||
/*
|
||||
* Make sure that the partitioning clause is compatible with
|
||||
* existing constraints
|
||||
*/
|
||||
hypo_table_check_constraints_compatibility(entry);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (boundspec)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -100,3 +100,39 @@ SELECT COUNT(*) AS nb FROM hypopg_create_index('CREATE INDEX ON part_multi_1_q1
|
|||
|
||||
SELECT COUNT(*) AS nb FROM hypopg_create_index('CREATE INDEX ON hypo_part_multi_1 (dpt)');
|
||||
SELECT COUNT(*) AS nb FROM hypopg_create_index('CREATE INDEX ON hypo_part_multi_1_q1 (dpt)');
|
||||
|
||||
-- pk constraint check
|
||||
CREATE TABLE t_pk(id integer primary key, val text) PARTITION BY LIST (val);
|
||||
|
||||
CREATE TABLE hypo_t_pk(id integer primary key, val text);
|
||||
SELECT hypopg_partition_table('hypo_t_pk', 'PARTITION BY LIST (val)');
|
||||
|
||||
DROP TABLE hypo_t_pk;
|
||||
-- unique constraint check
|
||||
CREATE TABLE t_unique(id integer, val text) PARTITION BY LIST (val);
|
||||
CREATE UNIQUE INDEX ON t_unique(id);
|
||||
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
CREATE UNIQUE INDEX ON hypo_t_unique(id);
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
|
||||
DROP TABLE hypo_t_unique;
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
SELECT count(*) FROM hypopg_create_index('CREATE UNIQUE INDEX on hypo_t_unique (id)');
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
|
||||
DROP TABLE hypo_t_unique;
|
||||
CREATE TABLE hypo_t_unique(id integer, val text);
|
||||
SELECT hypopg_partition_table('hypo_t_unique', 'PARTITION BY LIST (val)');
|
||||
SELECT count(*) FROM hypopg_create_index('CREATE UNIQUE INDEX on hypo_t_unique (id)');
|
||||
|
||||
DROP TABLE t_unique;
|
||||
DROP TABLE hypo_t_unique;
|
||||
|
||||
-- constraint exclusion check
|
||||
CREATE TABLE t_constrext(c circle, val text, EXCLUDE USING gist(c WITH &&)) PARTITION BY LIST (val);
|
||||
|
||||
CREATE TABLE hypo_t_constrext(c circle, val text, EXCLUDE USING gist(c WITH &&));
|
||||
SELECT hypopg_partition_table('hypo_t_constrext', 'PARTITION BY LIST (val)');
|
||||
|
||||
DROP TABLE hypo_t_constrext;
|
||||
|
|
|
|||
Loading…
Reference in a new issue