change ErrorIfUnsupportedForeignConstraintExists function to use in two-way

improve-drop-trigger2
Onur Tirtir 2020-03-04 11:13:10 +03:00
parent ef1447e360
commit 8cec216af1
1 changed files with 238 additions and 170 deletions

View File

@ -114,7 +114,7 @@ ConstraintIsAForeignKeyToReferenceTable(char *constraintName, Oid relationId)
* of the referencing column. * of the referencing column.
* - If referencing table is a reference table, error out if the referenced * - If referencing table is a reference table, error out if the referenced
* table is not a reference table. * table is not a reference table.
* *
* Note that checks performed in this functions are only done via * Note that checks performed in this functions are only done via
* PostprocessAlterTableStmt function. There is another case ,allowed by Citus, * PostprocessAlterTableStmt function. There is another case ,allowed by Citus,
* but being errored out in this function, creating foreign key constraint * but being errored out in this function, creating foreign key constraint
@ -127,34 +127,35 @@ ConstraintIsAForeignKeyToReferenceTable(char *constraintName, Oid relationId)
* usage. * usage.
*/ */
void void
ErrorIfUnsupportedForeignConstraintExists(Relation relation, char referencingDistMethod, ErrorIfUnsupportedForeignConstraintExists(Relation relation, char distributionMethod,
Var *referencingDistKey, Var *distributionColumn,
uint32 referencingColocationId) uint32 colocationId)
{ {
ScanKeyData scanKey[1]; ScanKeyData scanKey[1];
int scanKeyCount = 1; int scanKeyCount = 1;
Oid relationOid = relation->rd_id; Oid relationOid = relation->rd_id;
bool relationNotReplicated = true;
bool relationIsCitusTable = IsCitusTable(relationOid); bool relationIsCitusTable = IsCitusTable(relationOid);
bool relationIsReferenceTable = (distributionMethod == DISTRIBUTE_BY_NONE);
bool relationNotReplicated = true;
if (relationIsCitusTable) if (relationIsCitusTable)
{ {
/* ALTER TABLE command is applied over single replicated table */ /* ALTER TABLE command is applied over single replicated table */
referencingNotReplicated = SingleReplicatedTable(referencingTableId); relationNotReplicated = SingleReplicatedTable(relationOid);
} }
else else
{ {
/* Creating single replicated table with foreign constraint */ /* Creating single replicated table with foreign constraint */
referencingNotReplicated = (ShardReplicationFactor == 1); relationNotReplicated = (ShardReplicationFactor == 1);
} }
Relation pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); Relation pgConstraint = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ,
relation->rd_id); CharGetDatum(CONSTRAINT_FOREIGN));
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint, SysScanDesc scanDescriptor = systable_beginscan(pgConstraint,
ConstraintRelidTypidNameIndexId, InvalidOid,
true, NULL, false, NULL,
scanKeyCount, scanKey); scanKeyCount, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor); HeapTuple heapTuple = systable_getnext(scanDescriptor);
@ -163,182 +164,249 @@ ErrorIfUnsupportedForeignConstraintExists(Relation relation, char referencingDis
{ {
Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple);
int referencingAttrIndex = -1; if (constraintForm->conrelid == relationOid)
char referencedDistMethod = 0;
Var *referencedDistKey = NULL;
int referencedAttrIndex = -1;
uint32 referencedColocationId = INVALID_COLOCATION_ID;
/* not a foreign key constraint, skip to next one */
if (constraintForm->contype != CONSTRAINT_FOREIGN)
{ {
heapTuple = systable_getnext(scanDescriptor); /* alias variables from the referencing table perspective */
continue; Oid referencingTableId = relationOid;
} char referencingDistMethod = distributionMethod;
Var *referencingDistKey = distributionColumn;
uint32 referencingColocationId = colocationId;
Oid referencedTableId = constraintForm->confrelid; bool referencingIsCitusTable = relationIsCitusTable;
bool referencedIsCitusTable = IsCitusTable(referencedTableId); bool referencingIsReferenceTable = relationIsReferenceTable;
bool referencingNotReplicated = relationNotReplicated;
bool selfReferencingTable = (referencingTableId == referencedTableId); char referencedDistMethod = 0;
Var *referencedDistKey = NULL;
uint32 referencedColocationId = INVALID_COLOCATION_ID;
/* TODO: will remove this block after implementing create table ref_table references local_table */ Oid referencedTableId = constraintForm->confrelid;
if (!referencedIsCitusTable && !selfReferencingTable) bool referencedIsCitusTable = IsCitusTable(referencedTableId);
{ int referencedAttrIndex = -1;
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create foreign key constraint"),
errdetail("Referenced table must be a distributed table"
" or a reference table.")));
}
/* set referenced table related variables here if table is referencing itself */ bool selfReferencingTable = (referencingTableId == referencedTableId);
if (!selfReferencingTable) /* TODO: will remove this block after implementing create table ref_table references local_table */
{ if (!referencedIsCitusTable && !selfReferencingTable)
referencedDistMethod = PartitionMethod(referencedTableId);
referencedDistKey = (referencedDistMethod == DISTRIBUTE_BY_NONE) ?
NULL :
DistPartitionKey(referencedTableId);
referencedColocationId = TableColocationId(referencedTableId);
}
else
{
referencedDistMethod = referencingDistMethod;
referencedDistKey = referencingDistKey;
referencedColocationId = referencingColocationId;
}
bool referencingIsReferenceTable = (referencingDistMethod == DISTRIBUTE_BY_NONE);
bool referencedIsReferenceTable = (referencedDistMethod == DISTRIBUTE_BY_NONE);
/*
* We support foreign keys between reference tables. No more checks
* are necessary.
*/
if (referencingIsReferenceTable && referencedIsReferenceTable)
{
heapTuple = systable_getnext(scanDescriptor);
continue;
}
/*
* Foreign keys from reference tables to distributed tables are not
* supported.
*/
if (referencingIsReferenceTable && !referencedIsReferenceTable)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint "
"since foreign keys from reference tables "
"to distributed tables are not supported"),
errdetail("A reference table can only have reference "
"keys to other reference tables or a local table "
"in coordinator")));
}
/*
* To enforce foreign constraints, tables must be co-located unless a
* reference table is referenced.
*/
if (referencingColocationId == INVALID_COLOCATION_ID ||
(referencingColocationId != referencedColocationId &&
!referencedIsReferenceTable))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint since "
"relations are not colocated or not referencing "
"a reference table"),
errdetail(
"A distributed table can only have foreign keys "
"if it is referencing another colocated hash "
"distributed table or a reference table")));
}
ForeignConstraintFindDistKeys(heapTuple,
referencingDistKey,
referencedDistKey,
&referencingAttrIndex,
&referencedAttrIndex);
bool referencingColumnsIncludeDistKey = (referencingAttrIndex != -1);
bool foreignConstraintOnDistKey =
(referencingColumnsIncludeDistKey && referencingAttrIndex ==
referencedAttrIndex);
/*
* If columns in the foreign key includes the distribution key from the
* referencing side, we do not allow update/delete operations through
* foreign key constraints (e.g. ... ON UPDATE SET NULL)
*/
if (referencingColumnsIncludeDistKey)
{
/*
* ON DELETE SET NULL and ON DELETE SET DEFAULT is not supported. Because we do
* not want to set partition column to NULL or default value.
*/
if (constraintForm->confdeltype == FKCONSTR_ACTION_SETNULL ||
constraintForm->confdeltype == FKCONSTR_ACTION_SETDEFAULT)
{ {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create foreign key constraint"), errmsg("cannot create foreign key constraint"),
errdetail("SET NULL or SET DEFAULT is not supported" errdetail("Referenced table must be a distributed table"
" in ON DELETE operation when distribution " " or a reference table.")));
"key is included in the foreign key constraint"))); }
/* set referenced table related variables here if table is referencing itself */
if (!selfReferencingTable)
{
if (referencedIsCitusTable)
{
referencedDistMethod = PartitionMethod(referencedTableId);
referencedDistKey = (referencedDistMethod == DISTRIBUTE_BY_NONE) ?
NULL :
DistPartitionKey(referencedTableId);
referencedColocationId = TableColocationId(referencedTableId);
}
}
else
{
referencedDistMethod = referencingDistMethod;
referencedDistKey = referencingDistKey;
referencedColocationId = referencingColocationId;
}
bool referencedIsReferenceTable = (referencedDistMethod ==
DISTRIBUTE_BY_NONE);
/* foreign key constraint between reference tables */
if (referencingIsReferenceTable && referencedIsReferenceTable)
{
/*
* We support foreign keys between reference tables. No more checks
* are necessary.
*/
heapTuple = systable_getnext(scanDescriptor);
continue;
}
/* foreign key constraint from a reference table to a local table */
if (referencingIsReferenceTable && !referencedIsCitusTable)
{
/*
* Upgrading "a local table having a foreign key constraint to another
* local table" to a reference table is not supported.
*/
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create foreign key constraint"),
errdetail(
"Local table having a foreign key constraint to another "
"local table cannot be upgraded to a reference table"),
errhint(
"To define foreign key constraint from a reference table to a "
"local table, use ALTER TABLE ADD CONSTRAINT ... command after "
"creating the reference table without a foreign key")));
}
/* distributed table to local table */
if (!referencingIsReferenceTable && referencingIsCitusTable &&
!referencedIsCitusTable)
{
ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot create foreign key constraint"),
errdetail(
"Foreign keys from distributed tables to local tables are not supported")));
}
if (referencingIsReferenceTable && referencedIsCitusTable &&
!referencedIsReferenceTable)
{
/*
* Foreign keys from reference tables to distributed tables are not
* supported.
*/
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint "
"since foreign keys from reference tables "
"to distributed tables are not supported"),
errdetail("A reference table can only have reference "
"keys to other reference tables or a local table "
"in coordinator")));
} }
/* /*
* ON UPDATE SET NULL, ON UPDATE SET DEFAULT and UPDATE CASCADE is not supported. * To enforce foreign constraints, tables must be co-located unless a
* Because we do not want to set partition column to NULL or default value. Also * reference table is referenced.
* cascading update operation would require re-partitioning. Updating partition
* column value is not allowed anyway even outside of foreign key concept.
*/ */
if (constraintForm->confupdtype == FKCONSTR_ACTION_SETNULL || if (referencingColocationId == INVALID_COLOCATION_ID ||
constraintForm->confupdtype == FKCONSTR_ACTION_SETDEFAULT || (referencingColocationId != referencedColocationId &&
constraintForm->confupdtype == FKCONSTR_ACTION_CASCADE) !referencedIsReferenceTable))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint since "
"relations are not colocated or not referencing "
"a reference table"),
errdetail(
"A distributed table can only have foreign keys "
"if it is referencing another colocated hash "
"distributed table or a reference table")));
}
int referencingAttrIndex = -1;
ForeignConstraintFindDistKeys(heapTuple,
referencingDistKey,
referencedDistKey,
&referencingAttrIndex,
&referencedAttrIndex);
bool referencingColumnsIncludeDistKey = (referencingAttrIndex != -1);
bool foreignConstraintOnDistKey =
(referencingColumnsIncludeDistKey && referencingAttrIndex ==
referencedAttrIndex);
/*
* If columns in the foreign key includes the distribution key from the
* referencing side, we do not allow update/delete operations through
* foreign key constraints (e.g. ... ON UPDATE SET NULL)
*/
if (referencingColumnsIncludeDistKey)
{
/*
* ON DELETE SET NULL and ON DELETE SET DEFAULT is not supported. Because we do
* not want to set partition column to NULL or default value.
*/
if (constraintForm->confdeltype == FKCONSTR_ACTION_SETNULL ||
constraintForm->confdeltype == FKCONSTR_ACTION_SETDEFAULT)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint"),
errdetail("SET NULL or SET DEFAULT is not supported"
" in ON DELETE operation when distribution "
"key is included in the foreign key constraint")));
}
/*
* ON UPDATE SET NULL, ON UPDATE SET DEFAULT and UPDATE CASCADE is not supported.
* Because we do not want to set partition column to NULL or default value. Also
* cascading update operation would require re-partitioning. Updating partition
* column value is not allowed anyway even outside of foreign key concept.
*/
if (constraintForm->confupdtype == FKCONSTR_ACTION_SETNULL ||
constraintForm->confupdtype == FKCONSTR_ACTION_SETDEFAULT ||
constraintForm->confupdtype == FKCONSTR_ACTION_CASCADE)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint"),
errdetail("SET NULL, SET DEFAULT or CASCADE is not "
"supported in ON UPDATE operation when "
"distribution key included in the foreign "
"constraint.")));
}
}
/*
* if tables are hash-distributed and colocated, we need to make sure that
* the distribution key is included in foreign constraint.
*/
if (!referencedIsReferenceTable && !foreignConstraintOnDistKey)
{ {
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint"), errmsg("cannot create foreign key constraint"),
errdetail("SET NULL, SET DEFAULT or CASCADE is not " errdetail("Foreign keys are supported in two cases, "
"supported in ON UPDATE operation when " "either in between two colocated tables including "
"distribution key included in the foreign " "partition column in the same ordinal in the both "
"constraint."))); "tables or from distributed to reference tables")));
} }
}
/* /*
* if tables are hash-distributed and colocated, we need to make sure that * We do not allow to create foreign constraints if shard replication factor is
* the distribution key is included in foreign constraint. * greater than 1. Because in our current design, multiple replicas may cause
*/ * locking problems and inconsistent shard contents.
if (!referencedIsReferenceTable && !foreignConstraintOnDistKey) *
{ * Note that we allow referenced table to be a reference table (e.g., not a
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), * single replicated table). This is allowed since (a) we are sure that
errmsg("cannot create foreign key constraint"), * placements always be in the same state (b) executors are aware of reference
errdetail("Foreign keys are supported in two cases, " * tables and handle concurrency related issues accordingly.
"either in between two colocated tables including " */
"partition column in the same ordinal in the both " if (!referencingNotReplicated)
"tables or from distributed to reference tables"))); {
} ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint"),
/* errdetail("Citus Community Edition currently supports "
* We do not allow to create foreign constraints if shard replication factor is "foreign key constraints only for "
* greater than 1. Because in our current design, multiple replicas may cause "\"citus.shard_replication_factor = 1\"."),
* locking problems and inconsistent shard contents. errhint(
* "Please change \"citus.shard_replication_factor to "
* Note that we allow referenced table to be a reference table (e.g., not a
* single replicated table). This is allowed since (a) we are sure that
* placements always be in the same state (b) executors are aware of reference
* tables and handle concurrency related issues accordingly.
*/
if (!referencingNotReplicated)
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create foreign key constraint"),
errdetail("Citus Community Edition currently supports "
"foreign key constraints only for "
"\"citus.shard_replication_factor = 1\"."),
errhint("Please change \"citus.shard_replication_factor to "
"1\". To learn more about using foreign keys with " "1\". To learn more about using foreign keys with "
"other replication factors, please contact us at " "other replication factors, please contact us at "
"https://citusdata.com/about/contact_us."))); "https://citusdata.com/about/contact_us.")));
}
}
else if (constraintForm->confrelid == relationOid)
{
/* alias variables from the referenced table perspective */
bool referencedIsReferenceTable = relationIsReferenceTable;
Oid referencingTableId = constraintForm->conrelid;
bool referencingIsCitusTable = IsCitusTable(referencingTableId);
/* foreign key constraint from a local table to a reference table */
if (!referencingIsCitusTable && referencedIsReferenceTable)
{
/*
* Upgrading "a local table referenced by another local table"
* to a reference table is supported, but not encouraged.
*/
ereport(WARNING, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg(
"should not create foreign key constraint in this way"),
errdetail(
"Local table referenced by another local table should not "
"be upgraded to a reference table"),
errhint(
"To define foreign key constraint from a local table to a "
"reference table properly, use ALTER TABLE ADD CONSTRAINT ... "
"command after creating the reference table without a foreign key")));
}
} }
heapTuple = systable_getnext(scanDescriptor); heapTuple = systable_getnext(scanDescriptor);
@ -430,7 +498,7 @@ ErrorIfUnsupportedAlterAddDropFKeyBetweenReferecenceAndLocalTable(Oid referencin
char referencingDistMethod = 0; char referencingDistMethod = 0;
bool referencingIsReferenceTable = false; bool referencingIsReferenceTable = false;
bool referencedIsDistributed = IsDistributedTable(referencedTableOid); bool referencedIsCitusTable = IsCitusTable(referencedTableOid);
char referencedDistMethod = 0; char referencedDistMethod = 0;
bool referencedIsReferenceTable = false; bool referencedIsReferenceTable = false;
@ -440,7 +508,7 @@ ErrorIfUnsupportedAlterAddDropFKeyBetweenReferecenceAndLocalTable(Oid referencin
referencingIsReferenceTable = (referencingDistMethod == DISTRIBUTE_BY_NONE); referencingIsReferenceTable = (referencingDistMethod == DISTRIBUTE_BY_NONE);
} }
if (referencedIsDistributed) if (referencedIsCitusTable)
{ {
referencedDistMethod = PartitionMethod(referencedTableOid); referencedDistMethod = PartitionMethod(referencedTableOid);
referencedIsReferenceTable = (referencedDistMethod == DISTRIBUTE_BY_NONE); referencedIsReferenceTable = (referencedDistMethod == DISTRIBUTE_BY_NONE);