|
|
|
@ -276,6 +276,8 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
|
|
|
|
|
int numattrs, int16 *attnums,
|
|
|
|
|
Oid *opclasses);
|
|
|
|
|
static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
|
|
|
|
|
static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
|
|
|
|
|
Oid *funcid);
|
|
|
|
|
static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
|
|
|
|
|
static void validateForeignKeyConstraint(char *conname,
|
|
|
|
|
Relation rel, Relation pkrel,
|
|
|
|
@ -358,6 +360,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD
|
|
|
|
|
static void ATPostAlterTypeParse(Oid oldId, char *cmd,
|
|
|
|
|
List **wqueue, LOCKMODE lockmode, bool rewrite);
|
|
|
|
|
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
|
|
|
|
|
static void TryReuseForeignKey(Oid oldId, Constraint *con);
|
|
|
|
|
static void change_owner_fix_column_acls(Oid relationOid,
|
|
|
|
|
Oid oldOwnerId, Oid newOwnerId);
|
|
|
|
|
static void change_owner_recurse_to_sequences(Oid relationOid,
|
|
|
|
@ -5620,6 +5623,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
numpks;
|
|
|
|
|
Oid indexOid;
|
|
|
|
|
Oid constrOid;
|
|
|
|
|
bool old_check_ok;
|
|
|
|
|
ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Grab an exclusive lock on the pk table, so that someone doesn't delete
|
|
|
|
@ -5736,6 +5741,13 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
|
|
|
|
|
errmsg("number of referencing and referenced columns for foreign key disagree")));
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* On the strength of a previous constraint, we might avoid scanning
|
|
|
|
|
* tables to validate this one. See below.
|
|
|
|
|
*/
|
|
|
|
|
old_check_ok = (fkconstraint->old_conpfeqop != NIL);
|
|
|
|
|
Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < numpks; i++)
|
|
|
|
|
{
|
|
|
|
|
Oid pktype = pktypoid[i];
|
|
|
|
@ -5750,6 +5762,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
Oid ppeqop;
|
|
|
|
|
Oid ffeqop;
|
|
|
|
|
int16 eqstrategy;
|
|
|
|
|
Oid pfeqop_right;
|
|
|
|
|
|
|
|
|
|
/* We need several fields out of the pg_opclass entry */
|
|
|
|
|
cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
|
|
|
|
@ -5792,10 +5805,17 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
|
|
|
|
|
eqstrategy);
|
|
|
|
|
if (OidIsValid(pfeqop))
|
|
|
|
|
{
|
|
|
|
|
pfeqop_right = fktyped;
|
|
|
|
|
ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
|
|
|
|
|
eqstrategy);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
ffeqop = InvalidOid; /* keep compiler quiet */
|
|
|
|
|
{
|
|
|
|
|
/* keep compiler quiet */
|
|
|
|
|
pfeqop_right = InvalidOid;
|
|
|
|
|
ffeqop = InvalidOid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
|
|
|
|
|
{
|
|
|
|
@ -5817,7 +5837,10 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
target_typeids[1] = opcintype;
|
|
|
|
|
if (can_coerce_type(2, input_typeids, target_typeids,
|
|
|
|
|
COERCION_IMPLICIT))
|
|
|
|
|
{
|
|
|
|
|
pfeqop = ffeqop = ppeqop;
|
|
|
|
|
pfeqop_right = opcintype;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
|
|
|
|
@ -5833,6 +5856,77 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
format_type_be(fktype),
|
|
|
|
|
format_type_be(pktype))));
|
|
|
|
|
|
|
|
|
|
if (old_check_ok)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* When a pfeqop changes, revalidate the constraint. We could
|
|
|
|
|
* permit intra-opfamily changes, but that adds subtle complexity
|
|
|
|
|
* without any concrete benefit for core types. We need not
|
|
|
|
|
* assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
|
|
|
|
|
*/
|
|
|
|
|
old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
|
|
|
|
|
old_pfeqop_item = lnext(old_pfeqop_item);
|
|
|
|
|
}
|
|
|
|
|
if (old_check_ok)
|
|
|
|
|
{
|
|
|
|
|
Oid old_fktype;
|
|
|
|
|
Oid new_fktype;
|
|
|
|
|
CoercionPathType old_pathtype;
|
|
|
|
|
CoercionPathType new_pathtype;
|
|
|
|
|
Oid old_castfunc;
|
|
|
|
|
Oid new_castfunc;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Identify coercion pathways from each of the old and new FK-side
|
|
|
|
|
* column types to the right (foreign) operand type of the pfeqop.
|
|
|
|
|
* We may assume that pg_constraint.conkey is not changing.
|
|
|
|
|
*/
|
|
|
|
|
old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
|
|
|
|
|
new_fktype = fktype;
|
|
|
|
|
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
|
|
|
|
|
&old_castfunc);
|
|
|
|
|
new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
|
|
|
|
|
&new_castfunc);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Upon a change to the cast from the FK column to its pfeqop
|
|
|
|
|
* operand, revalidate the constraint. For this evaluation, a
|
|
|
|
|
* binary coercion cast is equivalent to no cast at all. While
|
|
|
|
|
* type implementors should design implicit casts with an eye
|
|
|
|
|
* toward consistency of operations like equality, we cannot assume
|
|
|
|
|
* here that they have done so.
|
|
|
|
|
*
|
|
|
|
|
* A function with a polymorphic argument could change behavior
|
|
|
|
|
* arbitrarily in response to get_fn_expr_argtype(). Therefore,
|
|
|
|
|
* when the cast destination is polymorphic, we only avoid
|
|
|
|
|
* revalidation if the input type has not changed at all. Given
|
|
|
|
|
* just the core data types and operator classes, this requirement
|
|
|
|
|
* prevents no would-be optimizations.
|
|
|
|
|
*
|
|
|
|
|
* If the cast converts from a base type to a domain thereon, then
|
|
|
|
|
* that domain type must be the opcintype of the unique index.
|
|
|
|
|
* Necessarily, the primary key column must then be of the domain
|
|
|
|
|
* type. Since the constraint was previously valid, all values on
|
|
|
|
|
* the foreign side necessarily exist on the primary side and in
|
|
|
|
|
* turn conform to the domain. Consequently, we need not treat
|
|
|
|
|
* domains specially here.
|
|
|
|
|
*
|
|
|
|
|
* Since we require that all collations share the same notion of
|
|
|
|
|
* equality (which they do, because texteq reduces to bitwise
|
|
|
|
|
* equality), we don't compare collation here.
|
|
|
|
|
*
|
|
|
|
|
* We need not directly consider the PK type. It's necessarily
|
|
|
|
|
* binary coercible to the opcintype of the unique index column,
|
|
|
|
|
* and ri_triggers.c will only deal with PK datums in terms of that
|
|
|
|
|
* opcintype. Changing the opcintype also changes pfeqop.
|
|
|
|
|
*/
|
|
|
|
|
old_check_ok = (new_pathtype == old_pathtype &&
|
|
|
|
|
new_castfunc == old_castfunc &&
|
|
|
|
|
(!IsPolymorphicType(pfeqop_right) ||
|
|
|
|
|
new_fktype == old_fktype));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pfeqoperators[i] = pfeqop;
|
|
|
|
|
ppeqoperators[i] = ppeqop;
|
|
|
|
|
ffeqoperators[i] = ffeqop;
|
|
|
|
@ -5877,10 +5971,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Tell Phase 3 to check that the constraint is satisfied by existing rows.
|
|
|
|
|
* We can skip this during table creation, or if requested explicitly by
|
|
|
|
|
* specifying NOT VALID in an ADD FOREIGN KEY command.
|
|
|
|
|
* We can skip this during table creation, when requested explicitly by
|
|
|
|
|
* specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
|
|
|
|
|
* recreating a constraint following a SET DATA TYPE operation that did not
|
|
|
|
|
* impugn its validity.
|
|
|
|
|
*/
|
|
|
|
|
if (!fkconstraint->skip_validation)
|
|
|
|
|
if (!old_check_ok && !fkconstraint->skip_validation)
|
|
|
|
|
{
|
|
|
|
|
NewConstraint *newcon;
|
|
|
|
|
|
|
|
|
@ -6330,6 +6426,35 @@ transformFkeyCheckAttrs(Relation pkrel,
|
|
|
|
|
return indexoid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* findFkeyCast -
|
|
|
|
|
*
|
|
|
|
|
* Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
|
|
|
|
|
* Caller has equal regard for binary coercibility and for an exact match.
|
|
|
|
|
*/
|
|
|
|
|
static CoercionPathType
|
|
|
|
|
findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
|
|
|
|
|
{
|
|
|
|
|
CoercionPathType ret;
|
|
|
|
|
|
|
|
|
|
if (targetTypeId == sourceTypeId)
|
|
|
|
|
{
|
|
|
|
|
ret = COERCION_PATH_RELABELTYPE;
|
|
|
|
|
*funcid = InvalidOid;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ret = find_coercion_pathway(targetTypeId, sourceTypeId,
|
|
|
|
|
COERCION_IMPLICIT, funcid);
|
|
|
|
|
if (ret == COERCION_PATH_NONE)
|
|
|
|
|
/* A previously-relied-upon cast is now gone. */
|
|
|
|
|
elog(ERROR, "could not find cast from %u to %u",
|
|
|
|
|
sourceTypeId, targetTypeId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Permissions checks for ADD FOREIGN KEY */
|
|
|
|
|
static void
|
|
|
|
|
checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
|
|
|
|
@ -7717,6 +7842,7 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
|
|
|
|
|
foreach(lcmd, stmt->cmds)
|
|
|
|
|
{
|
|
|
|
|
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
|
|
|
|
|
Constraint *con;
|
|
|
|
|
|
|
|
|
|
switch (cmd->subtype)
|
|
|
|
|
{
|
|
|
|
@ -7730,6 +7856,12 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
|
|
|
|
|
lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
|
|
|
|
|
break;
|
|
|
|
|
case AT_AddConstraint:
|
|
|
|
|
Assert(IsA(cmd->def, Constraint));
|
|
|
|
|
con = (Constraint *) cmd->def;
|
|
|
|
|
/* rewriting neither side of a FK */
|
|
|
|
|
if (con->contype == CONSTR_FOREIGN &&
|
|
|
|
|
!rewrite && !tab->rewrite)
|
|
|
|
|
TryReuseForeignKey(oldId, con);
|
|
|
|
|
tab->subcmds[AT_PASS_OLD_CONSTR] =
|
|
|
|
|
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
|
|
|
|
|
break;
|
|
|
|
@ -7751,7 +7883,7 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
|
|
|
|
|
/*
|
|
|
|
|
* Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
|
|
|
|
|
* for the real analysis, then mutates the IndexStmt based on that verdict.
|
|
|
|
|
*/
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
TryReuseIndex(Oid oldId, IndexStmt *stmt)
|
|
|
|
|
{
|
|
|
|
@ -7768,6 +7900,50 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Subroutine for ATPostAlterTypeParse().
|
|
|
|
|
*
|
|
|
|
|
* Stash the old P-F equality operator into the Constraint node, for possible
|
|
|
|
|
* use by ATAddForeignKeyConstraint() in determining whether revalidation of
|
|
|
|
|
* this constraint can be skipped.
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
TryReuseForeignKey(Oid oldId, Constraint *con)
|
|
|
|
|
{
|
|
|
|
|
HeapTuple tup;
|
|
|
|
|
Datum adatum;
|
|
|
|
|
bool isNull;
|
|
|
|
|
ArrayType *arr;
|
|
|
|
|
Oid *rawarr;
|
|
|
|
|
int numkeys;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
Assert(con->contype == CONSTR_FOREIGN);
|
|
|
|
|
Assert(con->old_conpfeqop == NIL); /* already prepared this node */
|
|
|
|
|
|
|
|
|
|
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
|
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
|
|
|
elog(ERROR, "cache lookup failed for constraint %u", oldId);
|
|
|
|
|
|
|
|
|
|
adatum = SysCacheGetAttr(CONSTROID, tup,
|
|
|
|
|
Anum_pg_constraint_conpfeqop, &isNull);
|
|
|
|
|
if (isNull)
|
|
|
|
|
elog(ERROR, "null conpfeqop for constraint %u", oldId);
|
|
|
|
|
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
|
|
|
|
numkeys = ARR_DIMS(arr)[0];
|
|
|
|
|
/* test follows the one in ri_FetchConstraintInfo() */
|
|
|
|
|
if (ARR_NDIM(arr) != 1 ||
|
|
|
|
|
ARR_HASNULL(arr) ||
|
|
|
|
|
ARR_ELEMTYPE(arr) != OIDOID)
|
|
|
|
|
elog(ERROR, "conpfeqop is not a 1-D Oid array");
|
|
|
|
|
rawarr = (Oid *) ARR_DATA_PTR(arr);
|
|
|
|
|
|
|
|
|
|
/* stash a List of the operator Oids in our Constraint node */
|
|
|
|
|
for (i = 0; i < numkeys; i++)
|
|
|
|
|
con->old_conpfeqop = lcons_oid(rawarr[i], con->old_conpfeqop);
|
|
|
|
|
|
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ALTER TABLE OWNER
|
|
|
|
|