@@ -254,6 +254,7 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
254254static void AlterSeqNamespaces (Relation classRel ,Relation rel ,
255255Oid oldNspOid ,Oid newNspOid ,
256256const char * newNspName ,LOCKMODE lockmode );
257+ static void ATExecValidateConstraint (Relation rel ,const char * constrName );
257258static int transformColumnNameList (Oid relId ,List * colList ,
258259int16 * attnums ,Oid * atttypids );
259260static int transformFkeyGetPrimaryKey (Relation pkrel ,Oid * indexOid ,
@@ -264,7 +265,7 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
264265int numattrs ,int16 * attnums ,
265266Oid * opclasses );
266267static void checkFkeyPermissions (Relation rel ,int16 * attnums ,int natts );
267- static void validateForeignKeyConstraint (Constraint * fkconstraint ,
268+ static void validateForeignKeyConstraint (char * conname ,
268269Relation rel ,Relation pkrel ,
269270Oid pkindOid ,Oid constraintOid );
270271static void createForeignKeyTriggers (Relation rel ,Constraint * fkconstraint ,
@@ -2649,7 +2650,7 @@ AlterTableGetLockLevel(List *cmds)
26492650 * though don't change the semantic results from normal data reads and writes.
26502651 * Delaying an ALTER TABLE behind currently active writes only delays the point
26512652 * where the new strategy begins to take effect, so there is no benefit in waiting.
2652- * Inthise case the minimum restriction applies: we don't currently allow
2653+ * Inthis case the minimum restriction applies: we don't currently allow
26532654 * concurrent catalog updates.
26542655 */
26552656case AT_SetStatistics :
@@ -2660,6 +2661,7 @@ AlterTableGetLockLevel(List *cmds)
26602661case AT_SetOptions :
26612662case AT_ResetOptions :
26622663case AT_SetStorage :
2664+ case AT_ValidateConstraint :
26632665cmd_lockmode = ShareUpdateExclusiveLock ;
26642666break ;
26652667
@@ -2887,6 +2889,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
28872889ATPrepAddInherit (rel );
28882890pass = AT_PASS_MISC ;
28892891break ;
2892+ case AT_ValidateConstraint :
28902893case AT_EnableTrig :/* ENABLE TRIGGER variants */
28912894case AT_EnableAlwaysTrig :
28922895case AT_EnableReplicaTrig :
@@ -3054,6 +3057,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
30543057case AT_AddIndexConstraint :/* ADD CONSTRAINT USING INDEX */
30553058ATExecAddIndexConstraint (tab ,rel , (IndexStmt * )cmd -> def ,lockmode );
30563059break ;
3060+ case AT_ValidateConstraint :
3061+ ATExecValidateConstraint (rel ,cmd -> name );
3062+ break ;
30573063case AT_DropConstraint :/* DROP CONSTRAINT */
30583064ATExecDropConstraint (rel ,cmd -> name ,cmd -> behavior ,
30593065 false, false,
@@ -3307,10 +3313,15 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
33073313 */
33083314refrel = heap_open (con -> refrelid ,ShareRowExclusiveLock );
33093315
3310- validateForeignKeyConstraint (fkconstraint ,rel ,refrel ,
3316+ validateForeignKeyConstraint (fkconstraint -> conname ,rel ,refrel ,
33113317con -> refindid ,
33123318con -> conid );
33133319
3320+ /*
3321+ * No need to mark the constraint row as validated,
3322+ * we did that when we inserted the row earlier.
3323+ */
3324+
33143325heap_close (refrel ,NoLock );
33153326}
33163327}
@@ -5509,6 +5520,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
55095520CONSTRAINT_FOREIGN ,
55105521fkconstraint -> deferrable ,
55115522fkconstraint -> initdeferred ,
5523+ !fkconstraint -> skip_validation ,
55125524RelationGetRelid (rel ),
55135525fkattnum ,
55145526numfks ,
@@ -5538,7 +5550,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
55385550
55395551/*
55405552 * Tell Phase 3 to check that the constraint is satisfied by existing rows
5541- * (we can skip this during table creation).
5553+ * We can skip this during table creation or if requested explicitly
5554+ * by specifying NOT VALID on an alter table statement.
55425555 */
55435556if (!fkconstraint -> skip_validation )
55445557{
@@ -5561,6 +5574,86 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
55615574heap_close (pkrel ,NoLock );
55625575}
55635576
5577+ /*
5578+ * ALTER TABLE VALIDATE CONSTRAINT
5579+ */
5580+ static void
5581+ ATExecValidateConstraint (Relation rel ,const char * constrName )
5582+ {
5583+ Relation conrel ;
5584+ Form_pg_constraint con ;
5585+ SysScanDesc scan ;
5586+ ScanKeyData key ;
5587+ HeapTuple tuple ;
5588+ bool found = false;
5589+ Oid conid ;
5590+
5591+ conrel = heap_open (ConstraintRelationId ,RowExclusiveLock );
5592+
5593+ /*
5594+ * Find and the target constraint
5595+ */
5596+ ScanKeyInit (& key ,
5597+ Anum_pg_constraint_conrelid ,
5598+ BTEqualStrategyNumber ,F_OIDEQ ,
5599+ ObjectIdGetDatum (RelationGetRelid (rel )));
5600+ scan = systable_beginscan (conrel ,ConstraintRelidIndexId ,
5601+ true,SnapshotNow ,1 ,& key );
5602+
5603+ while (HeapTupleIsValid (tuple = systable_getnext (scan )))
5604+ {
5605+ con = (Form_pg_constraint )GETSTRUCT (tuple );
5606+
5607+ if (strcmp (NameStr (con -> conname ),constrName )!= 0 )
5608+ continue ;
5609+
5610+ conid = HeapTupleGetOid (tuple );
5611+ found = true;
5612+ break ;
5613+ }
5614+
5615+ if (found && con -> contype == CONSTRAINT_FOREIGN && !con -> convalidated )
5616+ {
5617+ HeapTuple copyTuple = heap_copytuple (tuple );
5618+ Form_pg_constraint copy_con = (Form_pg_constraint )GETSTRUCT (copyTuple );
5619+ Relation refrel ;
5620+
5621+ /*
5622+ * Triggers are already in place on both tables, so a
5623+ * concurrent write that alters the result here is not
5624+ * possible. Normally we can run a query here to do the
5625+ * validation, which would only require AccessShareLock.
5626+ * In some cases, it is possible that we might need to
5627+ * fire triggers to perform the check, so we take a lock
5628+ * at RowShareLock level just in case.
5629+ */
5630+ refrel = heap_open (con -> confrelid ,RowShareLock );
5631+
5632+ validateForeignKeyConstraint ((char * )constrName ,rel ,refrel ,
5633+ con -> conindid ,
5634+ conid );
5635+
5636+ /*
5637+ * Now update the catalog, while we have the door open.
5638+ */
5639+ copy_con -> convalidated = true;
5640+ simple_heap_update (conrel ,& copyTuple -> t_self ,copyTuple );
5641+ CatalogUpdateIndexes (conrel ,copyTuple );
5642+ heap_freetuple (copyTuple );
5643+ heap_close (refrel ,NoLock );
5644+ }
5645+
5646+ systable_endscan (scan );
5647+ heap_close (conrel ,RowExclusiveLock );
5648+
5649+ if (!found )
5650+ {
5651+ ereport (ERROR ,
5652+ (errcode (ERRCODE_UNDEFINED_OBJECT ),
5653+ errmsg ("foreign key constraint \"%s\" of relation \"%s\" does not exist" ,
5654+ constrName ,RelationGetRelationName (rel ))));
5655+ }
5656+ }
55645657
55655658/*
55665659 * transformColumnNameList - transform list of column names
@@ -5866,7 +5959,7 @@ checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
58665959 * Caller must have opened and locked both relations appropriately.
58675960 */
58685961static void
5869- validateForeignKeyConstraint (Constraint * fkconstraint ,
5962+ validateForeignKeyConstraint (char * conname ,
58705963Relation rel ,
58715964Relation pkrel ,
58725965Oid pkindOid ,
@@ -5881,7 +5974,7 @@ validateForeignKeyConstraint(Constraint *fkconstraint,
58815974 */
58825975MemSet (& trig ,0 ,sizeof (trig ));
58835976trig .tgoid = InvalidOid ;
5884- trig .tgname = fkconstraint -> conname ;
5977+ trig .tgname = conname ;
58855978trig .tgenabled = TRIGGER_FIRES_ON_ORIGIN ;
58865979trig .tgisinternal = TRUE;
58875980trig .tgconstrrelid = RelationGetRelid (pkrel );