Commit 3de241db authored by Alvaro Herrera's avatar Alvaro Herrera

Foreign keys on partitioned tables

Author: Álvaro Herrera
Discussion: https://postgr.es/m/20171231194359.cvojcour423ulha4@alvherre.pgsql
Reviewed-by: Peter Eisentraut
parent 857f9c36
...@@ -368,7 +368,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -368,7 +368,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
specified check constraints). But the specified check constraints). But the
database will not assume that the constraint holds for all rows in database will not assume that the constraint holds for all rows in
the table, until it is validated by using the <literal>VALIDATE the table, until it is validated by using the <literal>VALIDATE
CONSTRAINT</literal> option. CONSTRAINT</literal> option. Foreign key constraints on partitioned
tables may not be declared <literal>NOT VALID</literal> at present.
</para> </para>
<para> <para>
......
...@@ -546,9 +546,12 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -546,9 +546,12 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</para> </para>
<para> <para>
Partitioned tables do not support <literal>EXCLUDE</literal> or Partitioned tables do not support <literal>EXCLUDE</literal> constraints;
<literal>FOREIGN KEY</literal> constraints; however, you can define however, you can define these constraints on individual partitions.
these constraints on individual partitions. Also, while it's possible to define <literal>PRIMARY KEY</literal>
constraints on partitioned tables, it is not supported to create foreign
keys cannot that reference them. This restriction will be lifted in a
future release.
</para> </para>
</listitem> </listitem>
...@@ -907,7 +910,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -907,7 +910,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
must have <literal>REFERENCES</literal> permission on the referenced table must have <literal>REFERENCES</literal> permission on the referenced table
(either the whole table, or the specific referenced columns). (either the whole table, or the specific referenced columns).
Note that foreign key constraints cannot be defined between temporary Note that foreign key constraints cannot be defined between temporary
tables and permanent tables. tables and permanent tables. Also note that while it is possible to
define a foreign key on a partitioned table, it is not possible to
declare a foreign key that references a partitioned table.
</para> </para>
<para> <para>
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "catalog/pg_operator.h" #include "catalog/pg_operator.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
...@@ -377,6 +378,242 @@ CreateConstraintEntry(const char *constraintName, ...@@ -377,6 +378,242 @@ CreateConstraintEntry(const char *constraintName,
return conOid; return conOid;
} }
/*
* CloneForeignKeyConstraints
* Clone foreign keys from a partitioned table to a newly acquired
* partition.
*
* relationId is a partition of parentId, so we can be certain that it has the
* same columns with the same datatypes. The columns may be in different
* order, though.
*
* The *cloned list is appended ClonedConstraint elements describing what was
* created.
*/
void
CloneForeignKeyConstraints(Oid parentId, Oid relationId, List **cloned)
{
Relation pg_constraint;
Relation parentRel;
Relation rel;
ScanKeyData key;
SysScanDesc scan;
TupleDesc tupdesc;
HeapTuple tuple;
AttrNumber *attmap;
parentRel = heap_open(parentId, NoLock); /* already got lock */
/* see ATAddForeignKeyConstraint about lock level */
rel = heap_open(relationId, AccessExclusiveLock);
pg_constraint = heap_open(ConstraintRelationId, RowShareLock);
tupdesc = RelationGetDescr(pg_constraint);
/*
* The constraint key may differ, if the columns in the partition are
* different. This map is used to convert them.
*/
attmap = convert_tuples_by_name_map(RelationGetDescr(rel),
RelationGetDescr(parentRel),
gettext_noop("could not convert row type"));
ScanKeyInit(&key,
Anum_pg_constraint_conrelid, BTEqualStrategyNumber,
F_OIDEQ, ObjectIdGetDatum(parentId));
scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
NULL, 1, &key);
while ((tuple = systable_getnext(scan)) != NULL)
{
Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
AttrNumber conkey[INDEX_MAX_KEYS];
AttrNumber mapped_conkey[INDEX_MAX_KEYS];
AttrNumber confkey[INDEX_MAX_KEYS];
Oid conpfeqop[INDEX_MAX_KEYS];
Oid conppeqop[INDEX_MAX_KEYS];
Oid conffeqop[INDEX_MAX_KEYS];
Constraint *fkconstraint;
ClonedConstraint *newc;
Oid constrOid;
ObjectAddress parentAddr,
childAddr;
int nelem;
int i;
ArrayType *arr;
Datum datum;
bool isnull;
/* only foreign keys */
if (constrForm->contype != CONSTRAINT_FOREIGN)
continue;
ObjectAddressSet(parentAddr, ConstraintRelationId,
HeapTupleGetOid(tuple));
datum = fastgetattr(tuple, Anum_pg_constraint_conkey,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conkey");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != INT2OID)
elog(ERROR, "conkey is not a 1-D smallint array");
memcpy(conkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber));
for (i = 0; i < nelem; i++)
mapped_conkey[i] = attmap[conkey[i] - 1];
datum = fastgetattr(tuple, Anum_pg_constraint_confkey,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null confkey");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != INT2OID)
elog(ERROR, "confkey is not a 1-D smallint array");
memcpy(confkey, ARR_DATA_PTR(arr), nelem * sizeof(AttrNumber));
datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conpfeqop");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conpfeqop is not a 1-D OID array");
memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
datum = fastgetattr(tuple, Anum_pg_constraint_conpfeqop,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conpfeqop");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conpfeqop is not a 1-D OID array");
memcpy(conpfeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
datum = fastgetattr(tuple, Anum_pg_constraint_conppeqop,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conppeqop");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conppeqop is not a 1-D OID array");
memcpy(conppeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
datum = fastgetattr(tuple, Anum_pg_constraint_conffeqop,
tupdesc, &isnull);
if (isnull)
elog(ERROR, "null conffeqop");
arr = DatumGetArrayTypeP(datum);
nelem = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
nelem < 1 ||
nelem > INDEX_MAX_KEYS ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "conffeqop is not a 1-D OID array");
memcpy(conffeqop, ARR_DATA_PTR(arr), nelem * sizeof(Oid));
constrOid =
CreateConstraintEntry(NameStr(constrForm->conname),
constrForm->connamespace,
CONSTRAINT_FOREIGN,
constrForm->condeferrable,
constrForm->condeferred,
constrForm->convalidated,
HeapTupleGetOid(tuple),
relationId,
mapped_conkey,
nelem,
InvalidOid, /* not a domain constraint */
constrForm->conindid, /* same index */
constrForm->confrelid, /* same foreign rel */
confkey,
conpfeqop,
conppeqop,
conffeqop,
nelem,
constrForm->confupdtype,
constrForm->confdeltype,
constrForm->confmatchtype,
NULL,
NULL,
NULL,
NULL,
false,
1, false, true);
ObjectAddressSet(childAddr, ConstraintRelationId, constrOid);
recordDependencyOn(&childAddr, &parentAddr, DEPENDENCY_INTERNAL_AUTO);
fkconstraint = makeNode(Constraint);
/* for now this is all we need */
fkconstraint->fk_upd_action = constrForm->confupdtype;
fkconstraint->fk_del_action = constrForm->confdeltype;
fkconstraint->deferrable = constrForm->condeferrable;
fkconstraint->initdeferred = constrForm->condeferred;
createForeignKeyTriggers(rel, constrForm->confrelid, fkconstraint,
constrOid, constrForm->conindid, false);
if (cloned)
{
/*
* Feed back caller about the constraints we created, so that they can
* set up constraint verification.
*/
newc = palloc(sizeof(ClonedConstraint));
newc->relid = relationId;
newc->refrelid = constrForm->confrelid;
newc->conindid = constrForm->conindid;
newc->conid = constrOid;
newc->constraint = fkconstraint;
*cloned = lappend(*cloned, newc);
}
}
systable_endscan(scan);
pfree(attmap);
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
PartitionDesc partdesc = RelationGetPartitionDesc(rel);
int i;
for (i = 0; i < partdesc->nparts; i++)
CloneForeignKeyConstraints(RelationGetRelid(rel),
partdesc->oids[i],
cloned);
}
heap_close(rel, NoLock); /* keep lock till commit */
heap_close(parentRel, NoLock);
heap_close(pg_constraint, RowShareLock);
}
/* /*
* Test whether given name is currently used as a constraint name * Test whether given name is currently used as a constraint name
......
This diff is collapsed.
...@@ -749,12 +749,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) ...@@ -749,12 +749,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"), errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate, parser_errposition(cxt->pstate,
constraint->location))); constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
/* /*
* Fill in the current attribute's name and throw it into the * Fill in the current attribute's name and throw it into the
...@@ -868,12 +862,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) ...@@ -868,12 +862,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"), errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate, parser_errposition(cxt->pstate,
constraint->location))); constraint->location)));
if (cxt->ispartitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("foreign key constraints are not supported on partitioned tables"),
parser_errposition(cxt->pstate,
constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint); cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break; break;
......
...@@ -788,20 +788,23 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) ...@@ -788,20 +788,23 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
char paramname[16]; char paramname[16];
const char *querysep; const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS]; Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i; int i;
/* ---------- /* ----------
* The query string built is * The query string built is
* SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...] * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...]
* FOR KEY SHARE OF x * FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. * corresponding PK attributes.
* ---------- * ----------
*/ */
initStringInfo(&querybuf); initStringInfo(&querybuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
fkrelname); fk_only, fkrelname);
querysep = "WHERE"; querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++) for (i = 0; i < riinfo->nkeys; i++)
{ {
...@@ -947,17 +950,21 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) ...@@ -947,17 +950,21 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
char paramname[16]; char paramname[16];
const char *querysep; const char *querysep;
Oid queryoids[RI_MAX_NUMKEYS]; Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
/* ---------- /* ----------
* The query string built is * The query string built is
* DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...] * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. * corresponding PK attributes.
* ---------- * ----------
*/ */
initStringInfo(&querybuf); initStringInfo(&querybuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname); appendStringInfo(&querybuf, "DELETE FROM %s%s",
fk_only, fkrelname);
querysep = "WHERE"; querysep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++) for (i = 0; i < riinfo->nkeys; i++)
{ {
...@@ -1118,10 +1125,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) ...@@ -1118,10 +1125,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
const char *querysep; const char *querysep;
const char *qualsep; const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS * 2]; Oid queryoids[RI_MAX_NUMKEYS * 2];
const char *fk_only;
/* ---------- /* ----------
* The query string built is * The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...] * UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]
* WHERE $n = fkatt1 [AND ...] * WHERE $n = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. Note that we are assuming * corresponding PK attributes. Note that we are assuming
...@@ -1131,8 +1139,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) ...@@ -1131,8 +1139,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
*/ */
initStringInfo(&querybuf); initStringInfo(&querybuf);
initStringInfo(&qualbuf); initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = ""; querysep = "";
qualsep = "WHERE"; qualsep = "WHERE";
for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++) for (i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
...@@ -1337,11 +1348,12 @@ ri_setnull(TriggerData *trigdata) ...@@ -1337,11 +1348,12 @@ ri_setnull(TriggerData *trigdata)
char paramname[16]; char paramname[16];
const char *querysep; const char *querysep;
const char *qualsep; const char *qualsep;
const char *fk_only;
Oid queryoids[RI_MAX_NUMKEYS]; Oid queryoids[RI_MAX_NUMKEYS];
/* ---------- /* ----------
* The query string built is * The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...] * UPDATE [ONLY] <fktable> SET fkatt1 = NULL [, ...]
* WHERE $1 = fkatt1 [AND ...] * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. * corresponding PK attributes.
...@@ -1349,8 +1361,11 @@ ri_setnull(TriggerData *trigdata) ...@@ -1349,8 +1361,11 @@ ri_setnull(TriggerData *trigdata)
*/ */
initStringInfo(&querybuf); initStringInfo(&querybuf);
initStringInfo(&qualbuf); initStringInfo(&qualbuf);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = ""; querysep = "";
qualsep = "WHERE"; qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++) for (i = 0; i < riinfo->nkeys; i++)
...@@ -1554,11 +1569,12 @@ ri_setdefault(TriggerData *trigdata) ...@@ -1554,11 +1569,12 @@ ri_setdefault(TriggerData *trigdata)
const char *querysep; const char *querysep;
const char *qualsep; const char *qualsep;
Oid queryoids[RI_MAX_NUMKEYS]; Oid queryoids[RI_MAX_NUMKEYS];
const char *fk_only;
int i; int i;
/* ---------- /* ----------
* The query string built is * The query string built is
* UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...] * UPDATE [ONLY] <fktable> SET fkatt1 = DEFAULT [, ...]
* WHERE $1 = fkatt1 [AND ...] * WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the * The type id's for the $ parameters are those of the
* corresponding PK attributes. * corresponding PK attributes.
...@@ -1567,7 +1583,10 @@ ri_setdefault(TriggerData *trigdata) ...@@ -1567,7 +1583,10 @@ ri_setdefault(TriggerData *trigdata)
initStringInfo(&querybuf); initStringInfo(&querybuf);
initStringInfo(&qualbuf); initStringInfo(&qualbuf);
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname); fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
appendStringInfo(&querybuf, "UPDATE %s%s SET",
fk_only, fkrelname);
querysep = ""; querysep = "";
qualsep = "WHERE"; qualsep = "WHERE";
for (i = 0; i < riinfo->nkeys; i++) for (i = 0; i < riinfo->nkeys; i++)
...@@ -1838,6 +1857,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) ...@@ -1838,6 +1857,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
RangeTblEntry *pkrte; RangeTblEntry *pkrte;
RangeTblEntry *fkrte; RangeTblEntry *fkrte;
const char *sep; const char *sep;
const char *fk_only;
int i; int i;
int save_nestlevel; int save_nestlevel;
char workmembuf[32]; char workmembuf[32];
...@@ -1894,8 +1914,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) ...@@ -1894,8 +1914,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
/*---------- /*----------
* The query string built is: * The query string built is:
* SELECT fk.keycols FROM ONLY relname fk * SELECT fk.keycols FROM [ONLY] relname fk
* LEFT OUTER JOIN ONLY pkrelname pk * LEFT OUTER JOIN pkrelname pk
* ON (pk.pkkeycol1=fk.keycol1 [AND ...]) * ON (pk.pkkeycol1=fk.keycol1 [AND ...])
* WHERE pk.pkkeycol1 IS NULL AND * WHERE pk.pkkeycol1 IS NULL AND
* For MATCH SIMPLE: * For MATCH SIMPLE:
...@@ -1920,9 +1940,11 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) ...@@ -1920,9 +1940,11 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
quoteRelationName(pkrelname, pk_rel); quoteRelationName(pkrelname, pk_rel);
quoteRelationName(fkrelname, fk_rel); quoteRelationName(fkrelname, fk_rel);
fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
"" : "ONLY ";
appendStringInfo(&querybuf, appendStringInfo(&querybuf,
" FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON", " FROM %s%s fk LEFT OUTER JOIN %s pk ON",
fkrelname, pkrelname); fk_only, fkrelname, pkrelname);
strcpy(pkattname, "pk."); strcpy(pkattname, "pk.");
strcpy(fkattname, "fk."); strcpy(fkattname, "fk.");
...@@ -2298,13 +2320,6 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk) ...@@ -2298,13 +2320,6 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
trigger->tgname, RelationGetRelationName(trig_rel)); trigger->tgname, RelationGetRelationName(trig_rel));
} }
else
{
if (riinfo->fk_relid != RelationGetRelid(trig_rel) ||
riinfo->pk_relid != trigger->tgconstrrelid)
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
trigger->tgname, RelationGetRelationName(trig_rel));
}
return riinfo; return riinfo;
} }
......
...@@ -7116,13 +7116,23 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables) ...@@ -7116,13 +7116,23 @@ getConstraints(Archive *fout, TableInfo tblinfo[], int numTables)
tbinfo->dobj.name); tbinfo->dobj.name);
resetPQExpBuffer(query); resetPQExpBuffer(query);
appendPQExpBuffer(query, if (fout->remoteVersion >= 110000)
"SELECT tableoid, oid, conname, confrelid, " appendPQExpBuffer(query,
"pg_catalog.pg_get_constraintdef(oid) AS condef " "SELECT tableoid, oid, conname, confrelid, "
"FROM pg_catalog.pg_constraint " "pg_catalog.pg_get_constraintdef(oid) AS condef "
"WHERE conrelid = '%u'::pg_catalog.oid " "FROM pg_catalog.pg_constraint "
"AND contype = 'f'", "WHERE conrelid = '%u'::pg_catalog.oid "
tbinfo->dobj.catId.oid); "AND conparentid = 0 "
"AND contype = 'f'",
tbinfo->dobj.catId.oid);
else
appendPQExpBuffer(query,
"SELECT tableoid, oid, conname, confrelid, "
"pg_catalog.pg_get_constraintdef(oid) AS condef "
"FROM pg_catalog.pg_constraint "
"WHERE conrelid = '%u'::pg_catalog.oid "
"AND contype = 'f'",
tbinfo->dobj.catId.oid);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
ntups = PQntuples(res); ntups = PQntuples(res);
...@@ -16374,18 +16384,28 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo) ...@@ -16374,18 +16384,28 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
} }
else if (coninfo->contype == 'f') else if (coninfo->contype == 'f')
{ {
char *only;
/*
* Foreign keys on partitioned tables are always declared as inheriting
* to partitions; for all other cases, emit them as applying ONLY
* directly to the named table, because that's how they work for
* regular inherited tables.
*/
only = tbinfo->relkind == RELKIND_PARTITIONED_TABLE ? "" : "ONLY ";
/* /*
* XXX Potentially wrap in a 'SET CONSTRAINTS OFF' block so that the * XXX Potentially wrap in a 'SET CONSTRAINTS OFF' block so that the
* current table data is not processed * current table data is not processed
*/ */
appendPQExpBuffer(q, "ALTER TABLE ONLY %s\n", appendPQExpBuffer(q, "ALTER TABLE %s%s\n",
fmtQualifiedDumpable(tbinfo)); only, fmtQualifiedDumpable(tbinfo));
appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n", appendPQExpBuffer(q, " ADD CONSTRAINT %s %s;\n",
fmtId(coninfo->dobj.name), fmtId(coninfo->dobj.name),
coninfo->condef); coninfo->condef);
appendPQExpBuffer(delq, "ALTER TABLE ONLY %s ", appendPQExpBuffer(delq, "ALTER TABLE %s%s ",
fmtQualifiedDumpable(tbinfo)); only, fmtQualifiedDumpable(tbinfo));
appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n", appendPQExpBuffer(delq, "DROP CONSTRAINT %s;\n",
fmtId(coninfo->dobj.name)); fmtId(coninfo->dobj.name));
......
...@@ -27,6 +27,19 @@ typedef enum ConstraintCategory ...@@ -27,6 +27,19 @@ typedef enum ConstraintCategory
CONSTRAINT_ASSERTION /* for future expansion */ CONSTRAINT_ASSERTION /* for future expansion */
} ConstraintCategory; } ConstraintCategory;
/*
* Used when cloning a foreign key constraint to a partition, so that the
* caller can optionally set up a verification pass for it.
*/
typedef struct ClonedConstraint
{
Oid relid;
Oid refrelid;
Oid conindid;
Oid conid;
Constraint *constraint;
} ClonedConstraint;
extern Oid CreateConstraintEntry(const char *constraintName, extern Oid CreateConstraintEntry(const char *constraintName,
Oid constraintNamespace, Oid constraintNamespace,
char constraintType, char constraintType,
...@@ -57,6 +70,9 @@ extern Oid CreateConstraintEntry(const char *constraintName, ...@@ -57,6 +70,9 @@ extern Oid CreateConstraintEntry(const char *constraintName,
bool conNoInherit, bool conNoInherit,
bool is_internal); bool is_internal);
extern void CloneForeignKeyConstraints(Oid parentId, Oid relationId,
List **cloned);
extern void RemoveConstraintById(Oid conId); extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname); extern void RenameConstraintById(Oid conId, const char *newname);
......
...@@ -74,6 +74,10 @@ extern void find_composite_type_dependencies(Oid typeOid, ...@@ -74,6 +74,10 @@ extern void find_composite_type_dependencies(Oid typeOid,
extern void check_of_type(HeapTuple typetuple); extern void check_of_type(HeapTuple typetuple);
extern void createForeignKeyTriggers(Relation rel, Oid refRelOid,
Constraint *fkconstraint, Oid constraintOid,
Oid indexOid, bool create_action);
extern void register_on_commit_action(Oid relid, OnCommitAction action); extern void register_on_commit_action(Oid relid, OnCommitAction action);
extern void remove_on_commit_action(Oid relid); extern void remove_on_commit_action(Oid relid);
......
...@@ -3305,10 +3305,6 @@ CREATE TABLE partitioned ( ...@@ -3305,10 +3305,6 @@ CREATE TABLE partitioned (
a int, a int,
b int b int
) PARTITION BY RANGE (a, (a+b+1)); ) PARTITION BY RANGE (a, (a+b+1));
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: foreign key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
^
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
ERROR: exclusion constraints are not supported on partitioned tables ERROR: exclusion constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
......
...@@ -281,16 +281,6 @@ CREATE TABLE partitioned ( ...@@ -281,16 +281,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail ) PARTITION BY LIST (a1, a2); -- fail
ERROR: cannot use "list" partition strategy with more than one column ERROR: cannot use "list" partition strategy with more than one column
-- unsupported constraint type for partitioned tables -- unsupported constraint type for partitioned tables
CREATE TABLE pkrel (
a int PRIMARY KEY
);
CREATE TABLE partitioned (
a int REFERENCES pkrel(a)
) PARTITION BY RANGE (a);
ERROR: foreign key constraints are not supported on partitioned tables
LINE 2: a int REFERENCES pkrel(a)
^
DROP TABLE pkrel;
CREATE TABLE partitioned ( CREATE TABLE partitioned (
a int, a int,
EXCLUDE USING gist (a WITH &&) EXCLUDE USING gist (a WITH &&)
......
This diff is collapsed.
...@@ -1209,6 +1209,31 @@ Inherits: test_foreign_constraints ...@@ -1209,6 +1209,31 @@ Inherits: test_foreign_constraints
DROP TABLE test_foreign_constraints_inh; DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints; DROP TABLE test_foreign_constraints;
DROP TABLE test_primary_constraints; DROP TABLE test_primary_constraints;
-- Test foreign key behavior
create table inh_fk_1 (a int primary key);
insert into inh_fk_1 values (1), (2), (3);
create table inh_fk_2 (x int primary key, y int references inh_fk_1 on delete cascade);
insert into inh_fk_2 values (11, 1), (22, 2), (33, 3);
create table inh_fk_2_child () inherits (inh_fk_2);
insert into inh_fk_2_child values (111, 1), (222, 2);
delete from inh_fk_1 where a = 1;
select * from inh_fk_1 order by 1;
a
---
2
3
(2 rows)
select * from inh_fk_2 order by 1, 2;
x | y
-----+---
22 | 2
33 | 3
111 | 1
222 | 2
(4 rows)
drop table inh_fk_1, inh_fk_2, inh_fk_2_child;
-- Test that parent and child CHECK constraints can be created in either order -- Test that parent and child CHECK constraints can be created in either order
create table p1(f1 int); create table p1(f1 int);
create table p1_c1() inherits(p1); create table p1_c1() inherits(p1);
......
...@@ -2035,7 +2035,6 @@ CREATE TABLE partitioned ( ...@@ -2035,7 +2035,6 @@ CREATE TABLE partitioned (
a int, a int,
b int b int
) PARTITION BY RANGE (a, (a+b+1)); ) PARTITION BY RANGE (a, (a+b+1));
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
-- cannot drop column that is part of the partition key -- cannot drop column that is part of the partition key
......
...@@ -298,14 +298,6 @@ CREATE TABLE partitioned ( ...@@ -298,14 +298,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail ) PARTITION BY LIST (a1, a2); -- fail
-- unsupported constraint type for partitioned tables -- unsupported constraint type for partitioned tables
CREATE TABLE pkrel (
a int PRIMARY KEY
);
CREATE TABLE partitioned (
a int REFERENCES pkrel(a)
) PARTITION BY RANGE (a);
DROP TABLE pkrel;
CREATE TABLE partitioned ( CREATE TABLE partitioned (
a int, a int,
EXCLUDE USING gist (a WITH &&) EXCLUDE USING gist (a WITH &&)
......
...@@ -1065,3 +1065,157 @@ alter table fktable2 drop constraint fktable2_f1_fkey; ...@@ -1065,3 +1065,157 @@ alter table fktable2 drop constraint fktable2_f1_fkey;
commit; commit;
drop table pktable2, fktable2; drop table pktable2, fktable2;
--
-- Foreign keys and partitioned tables
--
-- partitioned table in the referenced side are not allowed
CREATE TABLE fk_partitioned_pk (a int, b int, primary key (a, b))
PARTITION BY RANGE (a, b);
-- verify with create table first ...
CREATE TABLE fk_notpartitioned_fk (a int, b int,
FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk);
-- and then with alter table.
CREATE TABLE fk_notpartitioned_fk_2 (a int, b int);
ALTER TABLE fk_notpartitioned_fk_2 ADD FOREIGN KEY (a, b)
REFERENCES fk_partitioned_pk;
DROP TABLE fk_partitioned_pk, fk_notpartitioned_fk_2;
-- Creation of a partitioned hierarchy with irregular definitions
CREATE TABLE fk_notpartitioned_pk (fdrop1 int, a int, fdrop2 int, b int,
PRIMARY KEY (a, b));
ALTER TABLE fk_notpartitioned_pk DROP COLUMN fdrop1, DROP COLUMN fdrop2;
CREATE TABLE fk_partitioned_fk (b int, fdrop1 int, a int) PARTITION BY RANGE (a, b);
ALTER TABLE fk_partitioned_fk DROP COLUMN fdrop1;
CREATE TABLE fk_partitioned_fk_1 (fdrop1 int, fdrop2 int, a int, fdrop3 int, b int);
ALTER TABLE fk_partitioned_fk_1 DROP COLUMN fdrop1, DROP COLUMN fdrop2, DROP COLUMN fdrop3;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_1 FOR VALUES FROM (0,0) TO (1000,1000);
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_notpartitioned_pk;
CREATE TABLE fk_partitioned_fk_2 (b int, fdrop1 int, fdrop2 int, a int);
ALTER TABLE fk_partitioned_fk_2 DROP COLUMN fdrop1, DROP COLUMN fdrop2;
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2 FOR VALUES FROM (1000,1000) TO (2000,2000);
CREATE TABLE fk_partitioned_fk_3 (fdrop1 int, fdrop2 int, fdrop3 int, fdrop4 int, b int, a int)
PARTITION BY HASH (a);
ALTER TABLE fk_partitioned_fk_3 DROP COLUMN fdrop1, DROP COLUMN fdrop2,
DROP COLUMN fdrop3, DROP COLUMN fdrop4;
CREATE TABLE fk_partitioned_fk_3_0 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 0);
CREATE TABLE fk_partitioned_fk_3_1 PARTITION OF fk_partitioned_fk_3 FOR VALUES WITH (MODULUS 5, REMAINDER 1);
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3
FOR VALUES FROM (2000,2000) TO (3000,3000);
-- these inserts, targetting both the partition directly as well as the
-- partitioned table, should all fail
INSERT INTO fk_partitioned_fk (a,b) VALUES (500, 501);
INSERT INTO fk_partitioned_fk_1 (a,b) VALUES (500, 501);
INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501);
INSERT INTO fk_partitioned_fk_2 (a,b) VALUES (1500, 1501);
INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2500, 2502);
INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
INSERT INTO fk_partitioned_fk_3 (a,b) VALUES (2501, 2503);
-- but if we insert the values that make them valid, then they work
INSERT INTO fk_notpartitioned_pk VALUES (500, 501), (1500, 1501),
(2500, 2502), (2501, 2503);
INSERT INTO fk_partitioned_fk (a,b) VALUES (500, 501);
INSERT INTO fk_partitioned_fk (a,b) VALUES (1500, 1501);
INSERT INTO fk_partitioned_fk (a,b) VALUES (2500, 2502);
INSERT INTO fk_partitioned_fk (a,b) VALUES (2501, 2503);
-- this update fails because there is no referenced row
UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501;
-- but we can fix it thusly:
INSERT INTO fk_notpartitioned_pk (a,b) VALUES (2502, 2503);
UPDATE fk_partitioned_fk SET a = a + 1 WHERE a = 2501;
-- these updates would leave lingering rows in the referencing table; disallow
UPDATE fk_notpartitioned_pk SET b = 502 WHERE a = 500;
UPDATE fk_notpartitioned_pk SET b = 1502 WHERE a = 1500;
UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 2500;
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_fkey;
-- done.
DROP TABLE fk_notpartitioned_pk, fk_partitioned_fk;
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
-- actions
CREATE TABLE fk_notpartitioned_pk (a int, b int, primary key (a, b));
CREATE TABLE fk_partitioned_fk (a int default 2501, b int default 142857) PARTITION BY LIST (a);
CREATE TABLE fk_partitioned_fk_1 PARTITION OF fk_partitioned_fk FOR VALUES IN (NULL,500,501,502);
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
REFERENCES fk_notpartitioned_pk MATCH SIMPLE
ON DELETE SET NULL ON UPDATE SET NULL;
CREATE TABLE fk_partitioned_fk_2 PARTITION OF fk_partitioned_fk FOR VALUES IN (1500,1502);
CREATE TABLE fk_partitioned_fk_3 (a int, b int);
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_3 FOR VALUES IN (2500,2501,2502,2503);
-- this insert fails
INSERT INTO fk_partitioned_fk (a, b) VALUES (2502, 2503);
INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503);
-- but since the FK is MATCH SIMPLE, this one doesn't
INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, NULL);
-- now create the referenced row ...
INSERT INTO fk_notpartitioned_pk VALUES (2502, 2503);
--- and now the same insert work
INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503);
-- this always works
INSERT INTO fk_partitioned_fk (a,b) VALUES (NULL, NULL);
-- ON UPDATE SET NULL
SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b IS NULL ORDER BY a;
UPDATE fk_notpartitioned_pk SET a = a + 1 WHERE a = 2502;
SELECT tableoid::regclass, a, b FROM fk_partitioned_fk WHERE b IS NULL ORDER BY a;
-- ON DELETE SET NULL
INSERT INTO fk_partitioned_fk VALUES (2503, 2503);
SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL;
DELETE FROM fk_notpartitioned_pk;
SELECT count(*) FROM fk_partitioned_fk WHERE a IS NULL;
-- ON UPDATE/DELETE SET DEFAULT
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_fkey;
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
REFERENCES fk_notpartitioned_pk
ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
INSERT INTO fk_notpartitioned_pk VALUES (2502, 2503);
INSERT INTO fk_partitioned_fk_3 (a, b) VALUES (2502, 2503);
-- this fails, because the defaults for the referencing table are not present
-- in the referenced table:
UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502;
-- but inserting the row we can make it work:
INSERT INTO fk_notpartitioned_pk VALUES (2501, 142857);
UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502;
SELECT * FROM fk_partitioned_fk WHERE b = 142857;
-- ON UPDATE/DELETE CASCADE
ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_fkey;
ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
REFERENCES fk_notpartitioned_pk
ON DELETE CASCADE ON UPDATE CASCADE;
UPDATE fk_notpartitioned_pk SET a = 2502 WHERE a = 2501;
SELECT * FROM fk_partitioned_fk WHERE b = 142857;
-- Now you see it ...
SELECT * FROM fk_partitioned_fk WHERE b = 142857;
DELETE FROM fk_notpartitioned_pk WHERE b = 142857;
-- now you don't.
SELECT * FROM fk_partitioned_fk WHERE a = 142857;
-- verify that DROP works
DROP TABLE fk_partitioned_fk_2;
-- verify that attaching a table checks that the existing data satisfies the
-- constraint
CREATE TABLE fk_partitioned_fk_2 (a int, b int) PARTITION BY RANGE (b);
CREATE TABLE fk_partitioned_fk_2_1 PARTITION OF fk_partitioned_fk_2 FOR VALUES FROM (0) TO (1000);
CREATE TABLE fk_partitioned_fk_2_2 PARTITION OF fk_partitioned_fk_2 FOR VALUES FROM (1000) TO (2000);
INSERT INTO fk_partitioned_fk_2 VALUES (1600, 601), (1600, 1601);
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2
FOR VALUES IN (1600);
INSERT INTO fk_notpartitioned_pk VALUES (1600, 601), (1600, 1601);
ALTER TABLE fk_partitioned_fk ATTACH PARTITION fk_partitioned_fk_2
FOR VALUES IN (1600);
-- leave these tables around intentionally
...@@ -409,6 +409,18 @@ DROP TABLE test_foreign_constraints_inh; ...@@ -409,6 +409,18 @@ DROP TABLE test_foreign_constraints_inh;
DROP TABLE test_foreign_constraints; DROP TABLE test_foreign_constraints;
DROP TABLE test_primary_constraints; DROP TABLE test_primary_constraints;
-- Test foreign key behavior
create table inh_fk_1 (a int primary key);
insert into inh_fk_1 values (1), (2), (3);
create table inh_fk_2 (x int primary key, y int references inh_fk_1 on delete cascade);
insert into inh_fk_2 values (11, 1), (22, 2), (33, 3);
create table inh_fk_2_child () inherits (inh_fk_2);
insert into inh_fk_2_child values (111, 1), (222, 2);
delete from inh_fk_1 where a = 1;
select * from inh_fk_1 order by 1;
select * from inh_fk_2 order by 1, 2;
drop table inh_fk_1, inh_fk_2, inh_fk_2_child;
-- Test that parent and child CHECK constraints can be created in either order -- Test that parent and child CHECK constraints can be created in either order
create table p1(f1 int); create table p1(f1 int);
create table p1_c1() inherits(p1); create table p1_c1() inherits(p1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment