@@ -254,6 +254,7 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
254
254
static void AlterSeqNamespaces (Relation classRel , Relation rel ,
255
255
Oid oldNspOid , Oid newNspOid ,
256
256
const char * newNspName , LOCKMODE lockmode );
257
+ static void ATExecValidateConstraint (Relation rel , const char * constrName );
257
258
static int transformColumnNameList (Oid relId , List * colList ,
258
259
int16 * attnums , Oid * atttypids );
259
260
static int transformFkeyGetPrimaryKey (Relation pkrel , Oid * indexOid ,
@@ -264,7 +265,7 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
264
265
int numattrs , int16 * attnums ,
265
266
Oid * opclasses );
266
267
static void checkFkeyPermissions (Relation rel , int16 * attnums , int natts );
267
- static void validateForeignKeyConstraint (Constraint * fkconstraint ,
268
+ static void validateForeignKeyConstraint (char * conname ,
268
269
Relation rel , Relation pkrel ,
269
270
Oid pkindOid , Oid constraintOid );
270
271
static void createForeignKeyTriggers (Relation rel , Constraint * fkconstraint ,
@@ -2649,7 +2650,7 @@ AlterTableGetLockLevel(List *cmds)
2649
2650
* though don't change the semantic results from normal data reads and writes.
2650
2651
* Delaying an ALTER TABLE behind currently active writes only delays the point
2651
2652
* where the new strategy begins to take effect, so there is no benefit in waiting.
2652
- * In thise case the minimum restriction applies: we don't currently allow
2653
+ * In this case the minimum restriction applies: we don't currently allow
2653
2654
* concurrent catalog updates.
2654
2655
*/
2655
2656
case AT_SetStatistics :
@@ -2660,6 +2661,7 @@ AlterTableGetLockLevel(List *cmds)
2660
2661
case AT_SetOptions :
2661
2662
case AT_ResetOptions :
2662
2663
case AT_SetStorage :
2664
+ case AT_ValidateConstraint :
2663
2665
cmd_lockmode = ShareUpdateExclusiveLock ;
2664
2666
break ;
2665
2667
@@ -2887,6 +2889,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
2887
2889
ATPrepAddInherit (rel );
2888
2890
pass = AT_PASS_MISC ;
2889
2891
break ;
2892
+ case AT_ValidateConstraint :
2890
2893
case AT_EnableTrig : /* ENABLE TRIGGER variants */
2891
2894
case AT_EnableAlwaysTrig :
2892
2895
case AT_EnableReplicaTrig :
@@ -3054,6 +3057,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
3054
3057
case AT_AddIndexConstraint : /* ADD CONSTRAINT USING INDEX */
3055
3058
ATExecAddIndexConstraint (tab , rel , (IndexStmt * ) cmd -> def , lockmode );
3056
3059
break ;
3060
+ case AT_ValidateConstraint :
3061
+ ATExecValidateConstraint (rel , cmd -> name );
3062
+ break ;
3057
3063
case AT_DropConstraint : /* DROP CONSTRAINT */
3058
3064
ATExecDropConstraint (rel , cmd -> name , cmd -> behavior ,
3059
3065
false, false,
@@ -3307,10 +3313,15 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
3307
3313
*/
3308
3314
refrel = heap_open (con -> refrelid , ShareRowExclusiveLock );
3309
3315
3310
- validateForeignKeyConstraint (fkconstraint , rel , refrel ,
3316
+ validateForeignKeyConstraint (fkconstraint -> conname , rel , refrel ,
3311
3317
con -> refindid ,
3312
3318
con -> conid );
3313
3319
3320
+ /*
3321
+ * No need to mark the constraint row as validated,
3322
+ * we did that when we inserted the row earlier.
3323
+ */
3324
+
3314
3325
heap_close (refrel , NoLock );
3315
3326
}
3316
3327
}
@@ -5509,6 +5520,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
5509
5520
CONSTRAINT_FOREIGN ,
5510
5521
fkconstraint -> deferrable ,
5511
5522
fkconstraint -> initdeferred ,
5523
+ !fkconstraint -> skip_validation ,
5512
5524
RelationGetRelid (rel ),
5513
5525
fkattnum ,
5514
5526
numfks ,
@@ -5538,7 +5550,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
5538
5550
5539
5551
/*
5540
5552
* 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.
5542
5555
*/
5543
5556
if (!fkconstraint -> skip_validation )
5544
5557
{
@@ -5561,6 +5574,86 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
5561
5574
heap_close (pkrel , NoLock );
5562
5575
}
5563
5576
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
+ }
5564
5657
5565
5658
/*
5566
5659
* transformColumnNameList - transform list of column names
@@ -5866,7 +5959,7 @@ checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
5866
5959
* Caller must have opened and locked both relations appropriately.
5867
5960
*/
5868
5961
static void
5869
- validateForeignKeyConstraint (Constraint * fkconstraint ,
5962
+ validateForeignKeyConstraint (char * conname ,
5870
5963
Relation rel ,
5871
5964
Relation pkrel ,
5872
5965
Oid pkindOid ,
@@ -5881,7 +5974,7 @@ validateForeignKeyConstraint(Constraint *fkconstraint,
5881
5974
*/
5882
5975
MemSet (& trig , 0 , sizeof (trig ));
5883
5976
trig .tgoid = InvalidOid ;
5884
- trig .tgname = fkconstraint -> conname ;
5977
+ trig .tgname = conname ;
5885
5978
trig .tgenabled = TRIGGER_FIRES_ON_ORIGIN ;
5886
5979
trig .tgisinternal = TRUE;
5887
5980
trig .tgconstrrelid = RelationGetRelid (pkrel );
0 commit comments