@@ -276,6 +276,8 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
276276 int numattrs , int16 * attnums ,
277277 Oid * opclasses );
278278static void checkFkeyPermissions (Relation rel , int16 * attnums , int natts );
279+ static CoercionPathType findFkeyCast (Oid targetTypeId , Oid sourceTypeId ,
280+ Oid * funcid );
279281static void validateCheckConstraint (Relation rel , HeapTuple constrtup );
280282static void validateForeignKeyConstraint (char * conname ,
281283 Relation rel , Relation pkrel ,
@@ -358,6 +360,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD
358360static void ATPostAlterTypeParse (Oid oldId , char * cmd ,
359361 List * * wqueue , LOCKMODE lockmode , bool rewrite );
360362static void TryReuseIndex (Oid oldId , IndexStmt * stmt );
363+ static void TryReuseForeignKey (Oid oldId , Constraint * con );
361364static void change_owner_fix_column_acls (Oid relationOid ,
362365 Oid oldOwnerId , Oid newOwnerId );
363366static void change_owner_recurse_to_sequences (Oid relationOid ,
@@ -5620,6 +5623,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
56205623 numpks ;
56215624 Oid indexOid ;
56225625 Oid constrOid ;
5626+ bool old_check_ok ;
5627+ ListCell * old_pfeqop_item = list_head (fkconstraint -> old_conpfeqop );
56235628
56245629 /*
56255630 * Grab an exclusive lock on the pk table, so that someone doesn't delete
@@ -5736,6 +5741,13 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
57365741 (errcode (ERRCODE_INVALID_FOREIGN_KEY ),
57375742 errmsg ("number of referencing and referenced columns for foreign key disagree" )));
57385743
5744+ /*
5745+ * On the strength of a previous constraint, we might avoid scanning
5746+ * tables to validate this one. See below.
5747+ */
5748+ old_check_ok = (fkconstraint -> old_conpfeqop != NIL );
5749+ Assert (!old_check_ok || numfks == list_length (fkconstraint -> old_conpfeqop ));
5750+
57395751 for (i = 0 ; i < numpks ; i ++ )
57405752 {
57415753 Oid pktype = pktypoid [i ];
@@ -5750,6 +5762,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
57505762 Oid ppeqop ;
57515763 Oid ffeqop ;
57525764 int16 eqstrategy ;
5765+ Oid pfeqop_right ;
57535766
57545767 /* We need several fields out of the pg_opclass entry */
57555768 cla_ht = SearchSysCache1 (CLAOID , ObjectIdGetDatum (opclasses [i ]));
@@ -5792,10 +5805,17 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
57925805 pfeqop = get_opfamily_member (opfamily , opcintype , fktyped ,
57935806 eqstrategy );
57945807 if (OidIsValid (pfeqop ))
5808+ {
5809+ pfeqop_right = fktyped ;
57955810 ffeqop = get_opfamily_member (opfamily , fktyped , fktyped ,
57965811 eqstrategy );
5812+ }
57975813 else
5798- ffeqop = InvalidOid ; /* keep compiler quiet */
5814+ {
5815+ /* keep compiler quiet */
5816+ pfeqop_right = InvalidOid ;
5817+ ffeqop = InvalidOid ;
5818+ }
57995819
58005820 if (!(OidIsValid (pfeqop ) && OidIsValid (ffeqop )))
58015821 {
@@ -5817,7 +5837,10 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
58175837 target_typeids [1 ] = opcintype ;
58185838 if (can_coerce_type (2 , input_typeids , target_typeids ,
58195839 COERCION_IMPLICIT ))
5840+ {
58205841 pfeqop = ffeqop = ppeqop ;
5842+ pfeqop_right = opcintype ;
5843+ }
58215844 }
58225845
58235846 if (!(OidIsValid (pfeqop ) && OidIsValid (ffeqop )))
@@ -5833,6 +5856,77 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
58335856 format_type_be (fktype ),
58345857 format_type_be (pktype ))));
58355858
5859+ if (old_check_ok )
5860+ {
5861+ /*
5862+ * When a pfeqop changes, revalidate the constraint. We could
5863+ * permit intra-opfamily changes, but that adds subtle complexity
5864+ * without any concrete benefit for core types. We need not
5865+ * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
5866+ */
5867+ old_check_ok = (pfeqop == lfirst_oid (old_pfeqop_item ));
5868+ old_pfeqop_item = lnext (old_pfeqop_item );
5869+ }
5870+ if (old_check_ok )
5871+ {
5872+ Oid old_fktype ;
5873+ Oid new_fktype ;
5874+ CoercionPathType old_pathtype ;
5875+ CoercionPathType new_pathtype ;
5876+ Oid old_castfunc ;
5877+ Oid new_castfunc ;
5878+
5879+ /*
5880+ * Identify coercion pathways from each of the old and new FK-side
5881+ * column types to the right (foreign) operand type of the pfeqop.
5882+ * We may assume that pg_constraint.conkey is not changing.
5883+ */
5884+ old_fktype = tab -> oldDesc -> attrs [fkattnum [i ] - 1 ]-> atttypid ;
5885+ new_fktype = fktype ;
5886+ old_pathtype = findFkeyCast (pfeqop_right , old_fktype ,
5887+ & old_castfunc );
5888+ new_pathtype = findFkeyCast (pfeqop_right , new_fktype ,
5889+ & new_castfunc );
5890+
5891+ /*
5892+ * Upon a change to the cast from the FK column to its pfeqop
5893+ * operand, revalidate the constraint. For this evaluation, a
5894+ * binary coercion cast is equivalent to no cast at all. While
5895+ * type implementors should design implicit casts with an eye
5896+ * toward consistency of operations like equality, we cannot assume
5897+ * here that they have done so.
5898+ *
5899+ * A function with a polymorphic argument could change behavior
5900+ * arbitrarily in response to get_fn_expr_argtype(). Therefore,
5901+ * when the cast destination is polymorphic, we only avoid
5902+ * revalidation if the input type has not changed at all. Given
5903+ * just the core data types and operator classes, this requirement
5904+ * prevents no would-be optimizations.
5905+ *
5906+ * If the cast converts from a base type to a domain thereon, then
5907+ * that domain type must be the opcintype of the unique index.
5908+ * Necessarily, the primary key column must then be of the domain
5909+ * type. Since the constraint was previously valid, all values on
5910+ * the foreign side necessarily exist on the primary side and in
5911+ * turn conform to the domain. Consequently, we need not treat
5912+ * domains specially here.
5913+ *
5914+ * Since we require that all collations share the same notion of
5915+ * equality (which they do, because texteq reduces to bitwise
5916+ * equality), we don't compare collation here.
5917+ *
5918+ * We need not directly consider the PK type. It's necessarily
5919+ * binary coercible to the opcintype of the unique index column,
5920+ * and ri_triggers.c will only deal with PK datums in terms of that
5921+ * opcintype. Changing the opcintype also changes pfeqop.
5922+ */
5923+ old_check_ok = (new_pathtype == old_pathtype &&
5924+ new_castfunc == old_castfunc &&
5925+ (!IsPolymorphicType (pfeqop_right ) ||
5926+ new_fktype == old_fktype ));
5927+
5928+ }
5929+
58365930 pfeqoperators [i ] = pfeqop ;
58375931 ppeqoperators [i ] = ppeqop ;
58385932 ffeqoperators [i ] = ffeqop ;
@@ -5877,10 +5971,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
58775971
58785972 /*
58795973 * Tell Phase 3 to check that the constraint is satisfied by existing rows.
5880- * We can skip this during table creation, or if requested explicitly by
5881- * specifying NOT VALID in an ADD FOREIGN KEY command.
5974+ * We can skip this during table creation, when requested explicitly by
5975+ * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
5976+ * recreating a constraint following a SET DATA TYPE operation that did not
5977+ * impugn its validity.
58825978 */
5883- if (!fkconstraint -> skip_validation )
5979+ if (!old_check_ok && ! fkconstraint -> skip_validation )
58845980 {
58855981 NewConstraint * newcon ;
58865982
@@ -6330,6 +6426,35 @@ transformFkeyCheckAttrs(Relation pkrel,
63306426 return indexoid ;
63316427}
63326428
6429+ /*
6430+ * findFkeyCast -
6431+ *
6432+ * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
6433+ * Caller has equal regard for binary coercibility and for an exact match.
6434+ */
6435+ static CoercionPathType
6436+ findFkeyCast (Oid targetTypeId , Oid sourceTypeId , Oid * funcid )
6437+ {
6438+ CoercionPathType ret ;
6439+
6440+ if (targetTypeId == sourceTypeId )
6441+ {
6442+ ret = COERCION_PATH_RELABELTYPE ;
6443+ * funcid = InvalidOid ;
6444+ }
6445+ else
6446+ {
6447+ ret = find_coercion_pathway (targetTypeId , sourceTypeId ,
6448+ COERCION_IMPLICIT , funcid );
6449+ if (ret == COERCION_PATH_NONE )
6450+ /* A previously-relied-upon cast is now gone. */
6451+ elog (ERROR , "could not find cast from %u to %u" ,
6452+ sourceTypeId , targetTypeId );
6453+ }
6454+
6455+ return ret ;
6456+ }
6457+
63336458/* Permissions checks for ADD FOREIGN KEY */
63346459static void
63356460checkFkeyPermissions (Relation rel , int16 * attnums , int natts )
@@ -7717,6 +7842,7 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
77177842 foreach (lcmd , stmt -> cmds )
77187843 {
77197844 AlterTableCmd * cmd = (AlterTableCmd * ) lfirst (lcmd );
7845+ Constraint * con ;
77207846
77217847 switch (cmd -> subtype )
77227848 {
@@ -7730,6 +7856,12 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
77307856 lappend (tab -> subcmds [AT_PASS_OLD_INDEX ], cmd );
77317857 break ;
77327858 case AT_AddConstraint :
7859+ Assert (IsA (cmd -> def , Constraint ));
7860+ con = (Constraint * ) cmd -> def ;
7861+ /* rewriting neither side of a FK */
7862+ if (con -> contype == CONSTR_FOREIGN &&
7863+ !rewrite && !tab -> rewrite )
7864+ TryReuseForeignKey (oldId , con );
77337865 tab -> subcmds [AT_PASS_OLD_CONSTR ] =
77347866 lappend (tab -> subcmds [AT_PASS_OLD_CONSTR ], cmd );
77357867 break ;
@@ -7751,7 +7883,7 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
77517883/*
77527884 * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
77537885 * for the real analysis, then mutates the IndexStmt based on that verdict.
7754- */
7886+ */
77557887static void
77567888TryReuseIndex (Oid oldId , IndexStmt * stmt )
77577889{
@@ -7768,6 +7900,50 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
77687900 }
77697901}
77707902
7903+ /*
7904+ * Subroutine for ATPostAlterTypeParse().
7905+ *
7906+ * Stash the old P-F equality operator into the Constraint node, for possible
7907+ * use by ATAddForeignKeyConstraint() in determining whether revalidation of
7908+ * this constraint can be skipped.
7909+ */
7910+ static void
7911+ TryReuseForeignKey (Oid oldId , Constraint * con )
7912+ {
7913+ HeapTuple tup ;
7914+ Datum adatum ;
7915+ bool isNull ;
7916+ ArrayType * arr ;
7917+ Oid * rawarr ;
7918+ int numkeys ;
7919+ int i ;
7920+
7921+ Assert (con -> contype == CONSTR_FOREIGN );
7922+ Assert (con -> old_conpfeqop == NIL ); /* already prepared this node */
7923+
7924+ tup = SearchSysCache1 (CONSTROID , ObjectIdGetDatum (oldId ));
7925+ if (!HeapTupleIsValid (tup )) /* should not happen */
7926+ elog (ERROR , "cache lookup failed for constraint %u" , oldId );
7927+
7928+ adatum = SysCacheGetAttr (CONSTROID , tup ,
7929+ Anum_pg_constraint_conpfeqop , & isNull );
7930+ if (isNull )
7931+ elog (ERROR , "null conpfeqop for constraint %u" , oldId );
7932+ arr = DatumGetArrayTypeP (adatum ); /* ensure not toasted */
7933+ numkeys = ARR_DIMS (arr )[0 ];
7934+ /* test follows the one in ri_FetchConstraintInfo() */
7935+ if (ARR_NDIM (arr ) != 1 ||
7936+ ARR_HASNULL (arr ) ||
7937+ ARR_ELEMTYPE (arr ) != OIDOID )
7938+ elog (ERROR , "conpfeqop is not a 1-D Oid array" );
7939+ rawarr = (Oid * ) ARR_DATA_PTR (arr );
7940+
7941+ /* stash a List of the operator Oids in our Constraint node */
7942+ for (i = 0 ; i < numkeys ; i ++ )
7943+ con -> old_conpfeqop = lcons_oid (rawarr [i ], con -> old_conpfeqop );
7944+
7945+ ReleaseSysCache (tup );
7946+ }
77717947
77727948/*
77737949 * ALTER TABLE OWNER
0 commit comments