ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
     ADD <replaceable class="PARAMETER">table_constraint</replaceable>
     ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable>
+    ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]
+    VALIDATE CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
     DISABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
     ENABLE TRIGGER [ <replaceable class="PARAMETER">trigger_name</replaceable> | ALL | USER ]
    </varlistentry>
 
    <varlistentry>
-    <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+    <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable>
+          [ NOT VALID ]</literal></term>
     <listitem>
      <para>
       This form adds a new constraint to a table using the same syntax as
-      <xref linkend="SQL-CREATETABLE">.
+      <xref linkend="SQL-CREATETABLE">. Newly added foreign key constraints can
+      also be defined as <literal>NOT VALID</literal> to avoid the
+      potentially lengthy initial check that must otherwise be performed.
+      Constraint checks are skipped at create table time, so
+      <xref linkend="SQL-CREATETABLE"> does not contain this option.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>VALIDATE CONSTRAINT</literal></term>
+    <listitem>
+     <para>
+      This form validates a foreign key constraint that was previously created
+      as <literal>NOT VALID</literal>. Constraints already marked valid do not
+      cause an error response.
      </para>
     </listitem>
    </varlistentry>
 
                          CONSTRAINT_CHECK,     /* Constraint Type */
                          false,    /* Is Deferrable */
                          false,    /* Is Deferred */
+                         true,     /* Is Validated */
                          RelationGetRelid(rel),        /* relation */
                          attNos,       /* attrs in the constraint */
                          keycount,     /* # attrs in the constraint */
 
                                   constraintType,
                                   deferrable,
                                   initdeferred,
+                                  true,
                                   RelationGetRelid(heapRelation),
                                   indexInfo->ii_KeyAttrNumbers,
                                   indexInfo->ii_NumIndexAttrs,
 
                      char constraintType,
                      bool isDeferrable,
                      bool isDeferred,
+                     bool isValidated,
                      Oid relId,
                      const int16 *constraintKey,
                      int constraintNKeys,
    values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
    values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable);
    values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred);
+   values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
    values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
    values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
    values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId);
 
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
                   Oid oldNspOid, Oid newNspOid,
                   const char *newNspName, LOCKMODE lockmode);
+static void ATExecValidateConstraint(Relation rel, const char *constrName);
 static int transformColumnNameList(Oid relId, List *colList,
                        int16 *attnums, Oid *atttypids);
 static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
                        int numattrs, int16 *attnums,
                        Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
-static void validateForeignKeyConstraint(Constraint *fkconstraint,
+static void validateForeignKeyConstraint(char *conname,
                             Relation rel, Relation pkrel,
                             Oid pkindOid, Oid constraintOid);
 static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
             * though don't change the semantic results from normal data reads and writes.
             * Delaying an ALTER TABLE behind currently active writes only delays the point
             * where the new strategy begins to take effect, so there is no benefit in waiting.
-            * In thise case the minimum restriction applies: we don't currently allow
+            * In this case the minimum restriction applies: we don't currently allow
             * concurrent catalog updates.
             */
            case AT_SetStatistics:
            case AT_SetOptions:
            case AT_ResetOptions:
            case AT_SetStorage:
+           case AT_ValidateConstraint:
                cmd_lockmode = ShareUpdateExclusiveLock;
                break;
 
            ATPrepAddInherit(rel);
            pass = AT_PASS_MISC;
            break;
+       case AT_ValidateConstraint:
        case AT_EnableTrig:     /* ENABLE TRIGGER variants */
        case AT_EnableAlwaysTrig:
        case AT_EnableReplicaTrig:
        case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
            ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
            break;
+       case AT_ValidateConstraint:
+           ATExecValidateConstraint(rel, cmd->name);
+           break;
        case AT_DropConstraint: /* DROP CONSTRAINT */
            ATExecDropConstraint(rel, cmd->name, cmd->behavior,
                                 false, false,
                 */
                refrel = heap_open(con->refrelid, ShareRowExclusiveLock);
 
-               validateForeignKeyConstraint(fkconstraint, rel, refrel,
+               validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
                                             con->refindid,
                                             con->conid);
 
+               /*
+                * No need to mark the constraint row as validated,
+                * we did that when we inserted the row earlier.
+                */
+
                heap_close(refrel, NoLock);
            }
        }
                                      CONSTRAINT_FOREIGN,
                                      fkconstraint->deferrable,
                                      fkconstraint->initdeferred,
+                                     !fkconstraint->skip_validation,
                                      RelationGetRelid(rel),
                                      fkattnum,
                                      numfks,
 
    /*
     * Tell Phase 3 to check that the constraint is satisfied by existing rows
-    * (we can skip this during table creation).
+    * We can skip this during table creation or if requested explicitly
+    * by specifying NOT VALID on an alter table statement.
     */
    if (!fkconstraint->skip_validation)
    {
    heap_close(pkrel, NoLock);
 }
 
+/*
+ * ALTER TABLE VALIDATE CONSTRAINT
+ */
+static void
+ATExecValidateConstraint(Relation rel, const char *constrName)
+{
+   Relation    conrel;
+   Form_pg_constraint con;
+   SysScanDesc scan;
+   ScanKeyData key;
+   HeapTuple   tuple;
+   bool        found = false;
+   Oid         conid;
+
+   conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+   /*
+    * Find and the target constraint
+    */
+   ScanKeyInit(&key,
+               Anum_pg_constraint_conrelid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(RelationGetRelid(rel)));
+   scan = systable_beginscan(conrel, ConstraintRelidIndexId,
+                             true, SnapshotNow, 1, &key);
+
+   while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+   {
+       con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+       if (strcmp(NameStr(con->conname), constrName) != 0)
+           continue;
+
+       conid = HeapTupleGetOid(tuple);
+       found = true;
+       break;
+   }
+
+   if (found && con->contype == CONSTRAINT_FOREIGN && !con->convalidated)
+   {
+       HeapTuple   copyTuple = heap_copytuple(tuple);
+       Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
+       Relation    refrel;
+
+       /*
+        * Triggers are already in place on both tables, so a
+        * concurrent write that alters the result here is not
+        * possible. Normally we can run a query here to do the
+        * validation, which would only require AccessShareLock.
+        * In some cases, it is possible that we might need to
+        * fire triggers to perform the check, so we take a lock
+        * at RowShareLock level just in case.
+        */
+       refrel = heap_open(con->confrelid, RowShareLock);
+
+       validateForeignKeyConstraint((char *)constrName, rel, refrel,
+                                    con->conindid,
+                                    conid);
+
+       /*
+        * Now update the catalog, while we have the door open.
+        */
+       copy_con->convalidated = true;
+       simple_heap_update(conrel, ©Tuple->t_self, copyTuple);
+       CatalogUpdateIndexes(conrel, copyTuple);
+       heap_freetuple(copyTuple);
+       heap_close(refrel, NoLock);
+   }
+
+   systable_endscan(scan);
+   heap_close(conrel, RowExclusiveLock);
+
+   if (!found)
+   {
+       ereport(ERROR,
+               (errcode(ERRCODE_UNDEFINED_OBJECT),
+           errmsg("foreign key constraint \"%s\" of relation \"%s\" does not exist",
+                  constrName, RelationGetRelationName(rel))));
+   }
+}
 
 /*
  * transformColumnNameList - transform list of column names
  * Caller must have opened and locked both relations appropriately.
  */
 static void
-validateForeignKeyConstraint(Constraint *fkconstraint,
+validateForeignKeyConstraint(char *conname,
                             Relation rel,
                             Relation pkrel,
                             Oid pkindOid,
     */
    MemSet(&trig, 0, sizeof(trig));
    trig.tgoid = InvalidOid;
-   trig.tgname = fkconstraint->conname;
+   trig.tgname = conname;
    trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
    trig.tgisinternal = TRUE;
    trig.tgconstrrelid = RelationGetRelid(pkrel);
 
                                              CONSTRAINT_TRIGGER,
                                              stmt->deferrable,
                                              stmt->initdeferred,
+                                             true,
                                              RelationGetRelid(rel),
                                              NULL,     /* no conkey */
                                              0,
 
                          CONSTRAINT_CHECK,     /* Constraint Type */
                          false,    /* Is Deferrable */
                          false,    /* Is Deferred */
+                         true,     /* Is Validated */
                          InvalidOid,   /* not a relation constraint */
                          NULL,
                          0,
 
    UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
    UNTIL UPDATE USER USING
 
-   VACUUM VALID VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
+   VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
    VERBOSE VERSION_P VIEW VOLATILE
 
    WHEN WHERE WHITESPACE_P WINDOW WITH WITHOUT WORK WRAPPER WRITE
                    n->def = $2;
                    $$ = (Node *)n;
                }
+           /* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
+           | VALIDATE CONSTRAINT name
+               {
+                   AlterTableCmd *n = makeNode(AlterTableCmd);
+                   n->subtype = AT_ValidateConstraint;
+                   n->name = $3;
+                   $$ = (Node *)n;
+               }
            /* ALTER TABLE <name> DROP CONSTRAINT IF EXISTS <name> [RESTRICT|CASCADE] */
            | DROP CONSTRAINT IF_P EXISTS name opt_drop_behavior
                {
                    n->fk_matchtype     = $9;
                    n->fk_upd_action    = (char) ($10 >> 8);
                    n->fk_del_action    = (char) ($10 & 0xFF);
-                   n->skip_validation  = FALSE;
                    n->deferrable       = ($11 & 1) != 0;
                    n->initdeferred     = ($11 & 2) != 0;
+                   n->skip_validation  = false;
+                   $$ = (Node *)n;
+               }
+           | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
+               opt_column_list key_match key_actions
+               NOT VALID
+               {
+                   Constraint *n = makeNode(Constraint);
+                   n->contype = CONSTR_FOREIGN;
+                   n->location = @1;
+                   n->pktable          = $7;
+                   n->fk_attrs         = $4;
+                   n->pk_attrs         = $8;
+                   n->fk_matchtype     = $9;
+                   n->fk_upd_action    = (char) ($10 >> 8);
+                   n->fk_del_action    = (char) ($10 & 0xFF);
+                   n->skip_validation  = true;
                    $$ = (Node *)n;
                }
        ;
 
  * This is not a trigger procedure, but is called during ALTER TABLE
  * ADD FOREIGN KEY to validate the initial table contents.
  *
- * We expect that a ShareRowExclusiveLock or higher has been taken on rel and pkrel;
- * hence, we do not need to lock individual rows for the check.
+ *     We expect that the caller has made provision to prevent any problems
+ * caused by concurrent actions. This could be either by locking rel and
+ * pkrel at ShareRowExclusiveLock or higher, or by otherwise ensuring
+ * that triggers implementing the checks are already active.
+ * Hence, we do not need to lock individual rows for the check.
  *
  * If the check fails because the current user doesn't have permissions
  * to read both tables, return false to let our caller know that they will
 
        {
            printfPQExpBuffer(&buf,
                              "SELECT conname,\n"
-                "  pg_catalog.pg_get_constraintdef(r.oid, true) as condef\n"
-                             "FROM pg_catalog.pg_constraint r\n"
+                "  pg_catalog.pg_get_constraintdef(r.oid, true) as condef\n");
+           if (pset.sversion >= 90100)
+               appendPQExpBuffer(&buf, "  ,convalidated\n");
+           appendPQExpBuffer(&buf, "FROM pg_catalog.pg_constraint r\n"
                    "WHERE r.conrelid = '%s' AND r.contype = 'f' ORDER BY 1",
                              oid);
            result = PSQLexec(buf.data, false);
                                      PQgetvalue(result, i, 0),
                                      PQgetvalue(result, i, 1));
 
+                   if (strcmp(PQgetvalue(result, i, 2), "f") == 0)
+                       appendPQExpBuffer(&buf, " NOT VALID");
+
                    printTableAddFooter(&cont, buf.data);
                }
            }
 
    char        contype;        /* constraint type; see codes below */
    bool        condeferrable;  /* deferrable constraint? */
    bool        condeferred;    /* deferred by default? */
+   bool        convalidated;   /* constraint has been validated? */
 
    /*
     * conrelid and conkey are only meaningful if the constraint applies to a
  *     compiler constants for pg_constraint
  * ----------------
  */
-#define Natts_pg_constraint                    22
+#define Natts_pg_constraint                    23
 #define Anum_pg_constraint_conname         1
 #define Anum_pg_constraint_connamespace        2
 #define Anum_pg_constraint_contype         3
 #define Anum_pg_constraint_condeferrable   4
 #define Anum_pg_constraint_condeferred     5
-#define Anum_pg_constraint_conrelid            6
-#define Anum_pg_constraint_contypid            7
-#define Anum_pg_constraint_conindid            8
-#define Anum_pg_constraint_confrelid       9
-#define Anum_pg_constraint_confupdtype     10
-#define Anum_pg_constraint_confdeltype     11
-#define Anum_pg_constraint_confmatchtype   12
-#define Anum_pg_constraint_conislocal      13
-#define Anum_pg_constraint_coninhcount     14
-#define Anum_pg_constraint_conkey          15
-#define Anum_pg_constraint_confkey         16
-#define Anum_pg_constraint_conpfeqop       17
-#define Anum_pg_constraint_conppeqop       18
-#define Anum_pg_constraint_conffeqop       19
-#define Anum_pg_constraint_conexclop       20
-#define Anum_pg_constraint_conbin          21
-#define Anum_pg_constraint_consrc          22
+#define Anum_pg_constraint_convalidated        6
+#define Anum_pg_constraint_conrelid            7
+#define Anum_pg_constraint_contypid            8
+#define Anum_pg_constraint_conindid            9
+#define Anum_pg_constraint_confrelid       10
+#define Anum_pg_constraint_confupdtype     11
+#define Anum_pg_constraint_confdeltype     12
+#define Anum_pg_constraint_confmatchtype   13
+#define Anum_pg_constraint_conislocal      14
+#define Anum_pg_constraint_coninhcount     15
+#define Anum_pg_constraint_conkey          16
+#define Anum_pg_constraint_confkey         17
+#define Anum_pg_constraint_conpfeqop       18
+#define Anum_pg_constraint_conppeqop       19
+#define Anum_pg_constraint_conffeqop       20
+#define Anum_pg_constraint_conexclop       21
+#define Anum_pg_constraint_conbin          22
+#define Anum_pg_constraint_consrc          23
 
 
 /* Valid values for contype */
                      char constraintType,
                      bool isDeferrable,
                      bool isDeferred,
+                     bool isValidated,
                      Oid relId,
                      const int16 *constraintKey,
                      int constraintNKeys,
 
 extern void RemoveConstraintById(Oid conId);
 extern void RenameConstraintById(Oid conId, const char *newname);
+extern void SetValidatedConstraintById(Oid conId);
 
 extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
                     Oid objNamespace, const char *conname);
 
    AT_ReAddIndex,              /* internal to commands/tablecmds.c */
    AT_AddConstraint,           /* add constraint */
    AT_AddConstraintRecurse,    /* internal to commands/tablecmds.c */
+   AT_ValidateConstraint,      /* validate constraint */
    AT_ProcessedConstraint,     /* pre-processed add constraint (local in
                                 * parser/parse_utilcmd.c) */
    AT_AddIndexConstraint,      /* add constraint using existing index */
    Node       *transform;      /* transformation expr for ALTER TYPE */
    DropBehavior behavior;      /* RESTRICT or CASCADE for DROP cases */
    bool        missing_ok;     /* skip error if missing? */
+   bool        validated;
 } AlterTableCmd;
 
 
 
 PG_KEYWORD("using", USING, RESERVED_KEYWORD)
 PG_KEYWORD("vacuum", VACUUM, UNRESERVED_KEYWORD)
 PG_KEYWORD("valid", VALID, UNRESERVED_KEYWORD)
+PG_KEYWORD("validate", VALIDATE, UNRESERVED_KEYWORD)
 PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
 PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
 
 DELETE FROM tmp3 where a=5;
 -- Try (and succeed)
 ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
+ALTER TABLE tmp3 drop constraint tmpconstr;
+INSERT INTO tmp3 values (5,50);
+-- Try NOT VALID and then VALIDATE CONSTRAINT, but fails. Delete failure then re-validate
+ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
+ALTER TABLE tmp3 validate constraint tmpconstr;
+ERROR:  insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
+DETAIL:  Key (a)=(5) is not present in table "tmp2".
+-- Delete failing row
+DELETE FROM tmp3 where a=5;
+-- Try (and succeed) and repeat to show it works on already valid constraint
+ALTER TABLE tmp3 validate constraint tmpconstr;
+ALTER TABLE tmp3 validate constraint tmpconstr;
 -- Try (and fail) to create constraint from tmp5(a) to tmp4(a) - unique constraint on
 -- tmp4 is a,b
 ALTER TABLE tmp5 add constraint tmpconstr foreign key(a) references tmp4(a) match full;
 
 
 -- Try (and succeed)
 ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
+ALTER TABLE tmp3 drop constraint tmpconstr;
+
+INSERT INTO tmp3 values (5,50);
+
+-- Try NOT VALID and then VALIDATE CONSTRAINT, but fails. Delete failure then re-validate
+ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
+ALTER TABLE tmp3 validate constraint tmpconstr;
+
+-- Delete failing row
+DELETE FROM tmp3 where a=5;
+
+-- Try (and succeed) and repeat to show it works on already valid constraint
+ALTER TABLE tmp3 validate constraint tmpconstr;
+ALTER TABLE tmp3 validate constraint tmpconstr;
+
 
 -- Try (and fail) to create constraint from tmp5(a) to tmp4(a) - unique constraint on
 -- tmp4 is a,b