Check for (hypo) constraint compatibility with (hypo) partitioning

This commit is contained in:
Julien Rouhaud 2018-12-09 11:25:53 +01:00
parent f472fb12aa
commit 75c8987e2b
4 changed files with 373 additions and 0 deletions

View file

@ -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;

View file

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

View file

@ -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)
{

View file

@ -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;