*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1898,1904 ****
        <entry><structfield>convalidated</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
!       <entry>Has the constraint been validated? Can only be false for foreign keys</entry>
       </row>
  
       <row>
--- 1898,1904 ----
        <entry><structfield>convalidated</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
!       <entry>Has the constraint been validated? Can only be false for foreign keys and CHECK constraints</entry>
       </row>
  
       <row>
*** a/doc/src/sgml/ref/alter_domain.sgml
--- b/doc/src/sgml/ref/alter_domain.sgml
***************
*** 28,37 **** ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      { SET | DROP } NOT NULL
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
!     ADD <replaceable class="PARAMETER">domain_constraint</replaceable>
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      DROP CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
--- 28,39 ----
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      { SET | DROP } NOT NULL
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
!     ADD <replaceable class="PARAMETER">domain_constraint</replaceable> [ NOT VALID ]
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      DROP CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
+     VALIDATE CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable>
+ ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
  ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
      SET SCHEMA <replaceable class="PARAMETER">new_schema</replaceable>
***************
*** 70,82 **** ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
     </varlistentry>
  
     <varlistentry>
!     <term>ADD <replaceable class="PARAMETER">domain_constraint</replaceable></term>
      <listitem>
       <para>
        This form adds a new constraint to a domain using the same syntax as
        <xref linkend="SQL-CREATEDOMAIN">.
!       This will only succeed if all columns using the domain satisfy the
!       new constraint.
       </para>
      </listitem>
     </varlistentry>
--- 72,88 ----
     </varlistentry>
  
     <varlistentry>
!     <term>ADD <replaceable class="PARAMETER">domain_constraint</replaceable> [ NOT VALID ]</term>
      <listitem>
       <para>
        This form adds a new constraint to a domain using the same syntax as
        <xref linkend="SQL-CREATEDOMAIN">.
!       If NOT VALID is not specified,
!       the command will only succeed if all columns using the
!       domain satisfy the new constraint.
!       The constraint is going to be enforced on new data inserted into columns
!       using the domain in all cases, regardless of <literal>NOT VALID</>.
!       <literal>NOT VALID</> is only accepted for <literal>CHECK</> constraints.
       </para>
      </listitem>
     </varlistentry>
***************
*** 91,96 **** ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
--- 97,113 ----
     </varlistentry>
  
     <varlistentry>
+     <term>VALIDATE CONSTRAINT</term>
+     <listitem>
+      <para>
+       This form validates a constraint previously added, that is, verify that
+       all data in columns using the domain satisfy the specified constraint.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+ 
+    <varlistentry>
      <term>OWNER</term>
      <listitem>
       <para>
***************
*** 156,161 **** ALTER DOMAIN <replaceable class="PARAMETER">name</replaceable>
--- 173,188 ----
       </varlistentry>
  
       <varlistentry>
+       <term><replaceable class="PARAMETER">NOT VALID</replaceable></term>
+       <listitem>
+        <para>
+         Do not verify existing column data for constraint validity.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
+ 
+      <varlistentry>
        <term><literal>CASCADE</literal></term>
        <listitem>
         <para>
***************
*** 251,257 **** ALTER DOMAIN zipcode SET SCHEMA customers;
    <para>
     <command>ALTER DOMAIN</command> conforms to the <acronym>SQL</acronym>
     standard,
!    except for the <literal>OWNER</> and <literal>SET SCHEMA</> variants,
     which are <productname>PostgreSQL</productname> extensions.
    </para>
   </refsect1>
--- 278,286 ----
    <para>
     <command>ALTER DOMAIN</command> conforms to the <acronym>SQL</acronym>
     standard,
!    except for the <literal>OWNER</>, <literal>SET SCHEMA</> and
!    <literal>VALIDATE CONSTRAINT</> variants,
!    as well as the <literal>NOT VALID</> clause of the <literal>ADD CONSTRAINT</> variant,
     which are <productname>PostgreSQL</productname> extensions.
    </para>
   </refsect1>
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 42,50 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
      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 ]
--- 42,49 ----
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] )
      ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
      ADD <replaceable class="PARAMETER">table_constraint</replaceable> [ NOT VALID ]
+     ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable>
      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 ]
***************
*** 240,246 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
      <listitem>
       <para>
        This form adds a new constraint to a table using the same syntax as
!       <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
--- 239,245 ----
      <listitem>
       <para>
        This form adds a new constraint to a table using the same syntax as
!       <xref linkend="SQL-CREATETABLE">. Newly added foreign key and CHECK 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
***************
*** 250,266 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
     </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>
- 
-    <varlistentry>
      <term><literal>ADD <replaceable class="PARAMETER">table_constraint_using_index</replaceable></literal></term>
      <listitem>
       <para>
--- 249,254 ----
***************
*** 312,317 **** ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
--- 300,316 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>VALIDATE CONSTRAINT</literal></term>
+     <listitem>
+      <para>
+       This form validates a foreign key or CHECK constraint that was previously created
+       as <literal>NOT VALID</literal>. Constraints already marked valid do not
+       cause an error response.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>DROP CONSTRAINT [ IF EXISTS ]</literal></term>
      <listitem>
       <para>
*** a/src/backend/access/common/tupdesc.c
--- b/src/backend/access/common/tupdesc.c
***************
*** 200,205 **** CreateTupleDescCopyConstr(TupleDesc tupdesc)
--- 200,206 ----
  					cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
  				if (constr->check[i].ccbin)
  					cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
+ 				cpy->check[i].ccvalid = constr->check[i].ccvalid;
  			}
  		}
  
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 98,104 **** static Oid AddNewRelationType(const char *typeName,
  				   Oid new_array_type);
  static void RelationRemoveInheritance(Oid relid);
  static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
! 			  bool is_local, int inhcount);
  static void StoreConstraints(Relation rel, List *cooked_constraints);
  static bool MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
  							bool allow_merge, bool is_local);
--- 98,104 ----
  				   Oid new_array_type);
  static void RelationRemoveInheritance(Oid relid);
  static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
! 			  bool is_validated, bool is_local, int inhcount);
  static void StoreConstraints(Relation rel, List *cooked_constraints);
  static bool MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
  							bool allow_merge, bool is_local);
***************
*** 1845,1851 **** StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr)
   */
  static void
  StoreRelCheck(Relation rel, char *ccname, Node *expr,
! 			  bool is_local, int inhcount)
  {
  	char	   *ccbin;
  	char	   *ccsrc;
--- 1845,1851 ----
   */
  static void
  StoreRelCheck(Relation rel, char *ccname, Node *expr,
! 			  bool is_validated, bool is_local, int inhcount)
  {
  	char	   *ccbin;
  	char	   *ccsrc;
***************
*** 1906,1912 **** StoreRelCheck(Relation rel, char *ccname, Node *expr,
  						  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 */
--- 1906,1912 ----
  						  CONSTRAINT_CHECK,		/* Constraint Type */
  						  false,	/* Is Deferrable */
  						  false,	/* Is Deferred */
! 						  is_validated,
  						  RelationGetRelid(rel),		/* relation */
  						  attNos,		/* attrs in the constraint */
  						  keycount,		/* # attrs in the constraint */
***************
*** 1966,1972 **** StoreConstraints(Relation rel, List *cooked_constraints)
  				StoreAttrDefault(rel, con->attnum, con->expr);
  				break;
  			case CONSTR_CHECK:
! 				StoreRelCheck(rel, con->name, con->expr,
  							  con->is_local, con->inhcount);
  				numchecks++;
  				break;
--- 1966,1972 ----
  				StoreAttrDefault(rel, con->attnum, con->expr);
  				break;
  			case CONSTR_CHECK:
! 				StoreRelCheck(rel, con->name, con->expr, !con->skip_validation,
  							  con->is_local, con->inhcount);
  				numchecks++;
  				break;
***************
*** 2080,2085 **** AddRelationNewConstraints(Relation rel,
--- 2080,2086 ----
  		cooked->name = NULL;
  		cooked->attnum = colDef->attnum;
  		cooked->expr = expr;
+ 		cooked->skip_validation = false;
  		cooked->is_local = is_local;
  		cooked->inhcount = is_local ? 0 : 1;
  		cookedConstraints = lappend(cookedConstraints, cooked);
***************
*** 2193,2199 **** AddRelationNewConstraints(Relation rel,
  		/*
  		 * OK, store it.
  		 */
! 		StoreRelCheck(rel, ccname, expr, is_local, is_local ? 0 : 1);
  
  		numchecks++;
  
--- 2194,2201 ----
  		/*
  		 * OK, store it.
  		 */
! 		StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local,
! 					  is_local ? 0 : 1);
  
  		numchecks++;
  
***************
*** 2202,2207 **** AddRelationNewConstraints(Relation rel,
--- 2204,2210 ----
  		cooked->name = ccname;
  		cooked->attnum = 0;
  		cooked->expr = expr;
+ 		cooked->skip_validation = cdef->skip_validation;
  		cooked->is_local = is_local;
  		cooked->inhcount = is_local ? 0 : 1;
  		cookedConstraints = lappend(cookedConstraints, cooked);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 258,264 **** static void AlterIndexNamespaces(Relation classRel, Relation rel,
  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,
--- 258,265 ----
  static void AlterSeqNamespaces(Relation classRel, Relation rel,
  				   Oid oldNspOid, Oid newNspOid,
  				   const char *newNspName, LOCKMODE lockmode);
! static void ATExecValidateConstraint(Relation rel, const char *constrName,
! 						 bool recurse, bool recursing, LOCKMODE lockmode);
  static int transformColumnNameList(Oid relId, List *colList,
  						int16 *attnums, Oid *atttypids);
  static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
***************
*** 269,274 **** static Oid transformFkeyCheckAttrs(Relation pkrel,
--- 270,276 ----
  						int numattrs, int16 *attnums,
  						Oid *opclasses);
  static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
+ static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
  static void validateForeignKeyConstraint(char *conname,
  							 Relation rel, Relation pkrel,
  							 Oid pkindOid, Oid constraintOid);
***************
*** 560,565 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 562,568 ----
  			cooked->name = NULL;
  			cooked->attnum = attnum;
  			cooked->expr = colDef->cooked_default;
+ 			cooked->skip_validation = false;
  			cooked->is_local = true;	/* not used for defaults */
  			cooked->inhcount = 0;		/* ditto */
  			cookedDefaults = lappend(cookedDefaults, cooked);
***************
*** 1567,1572 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 1570,1576 ----
  					cooked->name = pstrdup(name);
  					cooked->attnum = 0; /* not used for constraints */
  					cooked->expr = expr;
+ 					cooked->skip_validation = false;
  					cooked->is_local = false;
  					cooked->inhcount = 1;
  					constraints = lappend(constraints, cooked);
***************
*** 2932,2938 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			ATPrepAddInherit(rel);
  			pass = AT_PASS_MISC;
  			break;
! 		case AT_ValidateConstraint:
  		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
  		case AT_EnableAlwaysTrig:
  		case AT_EnableReplicaTrig:
--- 2936,2949 ----
  			ATPrepAddInherit(rel);
  			pass = AT_PASS_MISC;
  			break;
! 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
! 			ATSimplePermissions(rel, ATT_TABLE);
! 			/* Recursion occurs during execution phase */
! 			/* No command-specific prep needed except saving recurse flag */
! 			if (recurse)
! 				cmd->subtype = AT_ValidateConstraintRecurse;
! 			pass = AT_PASS_MISC;
! 			break;
  		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
  		case AT_EnableAlwaysTrig:
  		case AT_EnableReplicaTrig:
***************
*** 3097,3104 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		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,
--- 3108,3119 ----
  		case AT_AddIndexConstraint:		/* ADD CONSTRAINT USING INDEX */
  			ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
  			break;
! 		case AT_ValidateConstraint:		/* VALIDATE CONSTRAINT */
! 			ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
! 			break;
! 		case AT_ValidateConstraintRecurse:	/* VALIDATE CONSTRAINT with
! 											 * recursion */
! 			ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
  			break;
  		case AT_DropConstraint:	/* DROP CONSTRAINT */
  			ATExecDropConstraint(rel, cmd->name, cmd->behavior,
***************
*** 5382,5400 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  										list_make1(copyObject(constr)),
  										recursing, !recursing);
  
! 	/* Add each constraint to Phase 3's queue */
  	foreach(lcon, newcons)
  	{
  		CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
- 		NewConstraint *newcon;
  
! 		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
! 		newcon->name = ccon->name;
! 		newcon->contype = ccon->contype;
! 		/* ExecQual wants implicit-AND format */
! 		newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
  
! 		tab->constraints = lappend(tab->constraints, newcon);
  
  		/* Save the actually assigned name if it was defaulted */
  		if (constr->conname == NULL)
--- 5397,5419 ----
  										list_make1(copyObject(constr)),
  										recursing, !recursing);
  
! 	/* Add each to-be-validated constraint to Phase 3's queue */
  	foreach(lcon, newcons)
  	{
  		CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
  
! 		if (!ccon->skip_validation)
! 		{
! 			NewConstraint *newcon;
  
! 			newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
! 			newcon->name = ccon->name;
! 			newcon->contype = ccon->contype;
! 			/* ExecQual wants implicit-AND format */
! 			newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
! 
! 			tab->constraints = lappend(tab->constraints, newcon);
! 		}
  
  		/* Save the actually assigned name if it was defaulted */
  		if (constr->conname == NULL)
***************
*** 5753,5761 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
  
  /*
   * ALTER TABLE VALIDATE CONSTRAINT
   */
  static void
! ATExecValidateConstraint(Relation rel, const char *constrName)
  {
  	Relation	conrel;
  	SysScanDesc scan;
--- 5772,5786 ----
  
  /*
   * ALTER TABLE VALIDATE CONSTRAINT
+  *
+  * XXX The reason we handle recursion here rather than at Phase 1 is because
+  * there's no good way to skip recursing when handling foreign keys: there is
+  * no need to lock children in that case, yet we wouldn't be able to avoid
+  * doing so at that level.
   */
  static void
! ATExecValidateConstraint(Relation rel, const char *constrName, bool recurse,
! 						 bool recursing, LOCKMODE lockmode)
  {
  	Relation	conrel;
  	SysScanDesc scan;
***************
*** 5779,5786 **** ATExecValidateConstraint(Relation rel, const char *constrName)
  	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
  	{
  		con = (Form_pg_constraint) GETSTRUCT(tuple);
! 		if (con->contype == CONSTRAINT_FOREIGN &&
! 			strcmp(NameStr(con->conname), constrName) == 0)
  		{
  			found = true;
  			break;
--- 5804,5810 ----
  	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
  	{
  		con = (Form_pg_constraint) GETSTRUCT(tuple);
! 		if (strcmp(NameStr(con->conname), constrName) == 0)
  		{
  			found = true;
  			break;
***************
*** 5790,5828 **** ATExecValidateConstraint(Relation rel, const char *constrName)
  	if (!found)
  		ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("foreign key constraint \"%s\" of relation \"%s\" does not exist",
  						constrName, RelationGetRelationName(rel))));
  
  	if (!con->convalidated)
  	{
! 		Oid			conid = HeapTupleGetOid(tuple);
! 		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, &copyTuple->t_self, copyTuple);
  		CatalogUpdateIndexes(conrel, copyTuple);
  		heap_freetuple(copyTuple);
- 
- 		heap_close(refrel, NoLock);
  	}
  
  	systable_endscan(scan);
--- 5814,5923 ----
  	if (!found)
  		ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" of relation \"%s\" does not exist",
! 						constrName, RelationGetRelationName(rel))));
! 
! 	if (con->contype != CONSTRAINT_FOREIGN &&
! 		con->contype != CONSTRAINT_CHECK)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint",
  						constrName, RelationGetRelationName(rel))));
  
  	if (!con->convalidated)
  	{
! 		HeapTuple	copyTuple;
! 		Form_pg_constraint copy_con;
  
! 		if (con->contype == CONSTRAINT_FOREIGN)
! 		{
! 			Oid			conid = HeapTupleGetOid(tuple);
! 			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);
! 			heap_close(refrel, NoLock);
! 
! 			/*
! 			 * Foreign keys do not inherit, so we purposedly ignore the
! 			 * recursion bit here
! 			 */
! 		}
! 		else if (con->contype == CONSTRAINT_CHECK)
! 		{
! 			List	   *children = NIL;
! 			ListCell   *child;
! 
! 			/*
! 			 * If we're recursing, the parent has already done this, so skip
! 			 * it.
! 			 */
! 			if (!recursing)
! 				children = find_all_inheritors(RelationGetRelid(rel),
! 											   lockmode, NULL);
! 
! 			/*
! 			 * For CHECK constraints, we must ensure that we only mark the
! 			 * constraint as validated on the parent if it's already validated
! 			 * on the children.
! 			 *
! 			 * We recurse before validating on the parent, to reduce risk of
! 			 * deadlocks.
! 			 */
! 			foreach(child, children)
! 			{
! 				Oid childoid = lfirst_oid(child);
! 				Relation	childrel;
! 
! 				if (childoid == RelationGetRelid(rel))
! 					continue;
! 
! 				/*
! 				 * If we are told not to recurse, there had better not be any
! 				 * child tables; else the addition would put them out of step.
! 				 */
! 				if (!recurse)
! 					ereport(ERROR,
! 							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 							 errmsg("constraint must be validated on child tables too")));
! 
! 				/* find_all_inheritors already got lock */
! 				childrel = heap_open(childoid, NoLock);
! 
! 				ATExecValidateConstraint(childrel, constrName, false,
! 										 true, lockmode);
! 				heap_close(childrel, NoLock);
! 			}
! 
! 			validateCheckConstraint(rel, tuple);
! 
! 			/*
! 			 * Invalidate relcache so that others see the new validated
! 			 * constraint.
! 			 */
! 			CacheInvalidateRelcache(rel);
! 		}
  
  		/*
  		 * Now update the catalog, while we have the door open.
  		 */
+ 		copyTuple = heap_copytuple(tuple);
+ 		copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
  		copy_con->convalidated = true;
  		simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
  		CatalogUpdateIndexes(conrel, copyTuple);
  		heap_freetuple(copyTuple);
  	}
  
  	systable_endscan(scan);
***************
*** 6128,6133 **** checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
--- 6223,6301 ----
  }
  
  /*
+  * Scan the existing rows in a table to verify they meet a proposed
+  * CHECK constraint.
+  *
+  * The caller must have opened and locked the relation appropriately.
+  */
+ static void
+ validateCheckConstraint(Relation rel, HeapTuple constrtup)
+ {
+ 	EState		   *estate;
+ 	Datum			val;
+ 	char		   *conbin;
+ 	Expr		   *origexpr;
+ 	List		   *exprstate;
+ 	TupleDesc		tupdesc;
+ 	HeapScanDesc	scan;
+ 	HeapTuple		tuple;
+ 	ExprContext	   *econtext;
+ 	MemoryContext	oldcxt;
+ 	TupleTableSlot *slot;
+ 	Form_pg_constraint constrForm;
+ 	bool			isnull;
+ 
+ 	constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
+ 
+ 	estate = CreateExecutorState();
+ 	/*
+ 	 * XXX this tuple doesn't really come from a syscache, but this doesn't
+ 	 * matter to SysCacheGetAttr, because it only wants to be able to fetch the
+ 	 * tupdesc
+ 	 */
+ 	val = SysCacheGetAttr(CONSTROID, constrtup, Anum_pg_constraint_conbin,
+ 						  &isnull);
+ 	if (isnull)
+ 		elog(ERROR, "null conbin for constraint %u",
+ 			 HeapTupleGetOid(constrtup));
+ 	conbin = TextDatumGetCString(val);
+ 	origexpr = (Expr *) stringToNode(conbin);
+ 	exprstate = (List *)
+ 		ExecPrepareExpr((Expr *) make_ands_implicit(origexpr), estate);
+ 
+ 	econtext = GetPerTupleExprContext(estate);
+ 	tupdesc = RelationGetDescr(rel);
+ 	slot = MakeSingleTupleTableSlot(tupdesc);
+ 	econtext->ecxt_scantuple = slot;
+ 
+ 	scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
+ 
+ 	/*
+ 	 * Switch to per-tuple memory context and reset it for each tuple
+ 	 * produced, so we don't leak memory.
+ 	 */
+ 	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+ 
+ 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ 	{
+ 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ 
+ 		if (!ExecQual(exprstate, econtext, true))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_CHECK_VIOLATION),
+ 					 errmsg("check constraint \"%s\" is violated by some row",
+ 							NameStr(constrForm->conname))));
+ 
+ 		ResetExprContext(econtext);
+ 	}
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ 	heap_endscan(scan);
+ 	ExecDropSingleTupleTableSlot(slot);
+ 	FreeExecutorState(estate);
+ }
+ 
+ /*
   * Scan the existing rows in a table to verify they meet a proposed FK
   * constraint.
   *
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 86,91 **** static Oid	findTypeSendFunction(List *procname, Oid typeOid);
--- 86,92 ----
  static Oid	findTypeTypmodinFunction(List *procname);
  static Oid	findTypeTypmodoutFunction(List *procname);
  static Oid	findTypeAnalyzeFunction(List *procname, Oid typeOid);
+ static void	validateDomainConstraint(Oid domainoid, char *ccbin);
  static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
  static void checkDomainOwner(HeapTuple tup);
  static void checkEnumOwner(HeapTuple tup);
***************
*** 1941,1954 **** AlterDomainAddConstraint(List *names, Node *newConstraint)
  	Relation	typrel;
  	HeapTuple	tup;
  	Form_pg_type typTup;
- 	List	   *rels;
- 	ListCell   *rt;
- 	EState	   *estate;
- 	ExprContext *econtext;
- 	char	   *ccbin;
- 	Expr	   *expr;
- 	ExprState  *exprstate;
  	Constraint *constr;
  
  	/* Make a TypeName so we can use standard type lookup machinery */
  	typename = makeTypeNameFromNameList(names);
--- 1942,1949 ----
  	Relation	typrel;
  	HeapTuple	tup;
  	Form_pg_type typTup;
  	Constraint *constr;
+ 	char	   *ccbin;
  
  	/* Make a TypeName so we can use standard type lookup machinery */
  	typename = makeTypeNameFromNameList(names);
***************
*** 2027,2036 **** AlterDomainAddConstraint(List *names, Node *newConstraint)
  								constr, NameStr(typTup->typname));
  
  	/*
! 	 * Test all values stored in the attributes based on the domain the
! 	 * constraint is being added to.
  	 */
! 	expr = (Expr *) stringToNode(ccbin);
  
  	/* Need an EState to run ExecEvalExpr */
  	estate = CreateExecutorState();
--- 2022,2150 ----
  								constr, NameStr(typTup->typname));
  
  	/*
! 	 * If requested to validate the constraint, test all values stored in the
! 	 * attributes based on the domain the constraint is being added to.
  	 */
! 	if (!constr->skip_validation)
! 		validateDomainConstraint(domainoid, ccbin);
! 
! 	/* Clean up */
! 	heap_close(typrel, RowExclusiveLock);
! }
! 
! /*
!  * AlterDomainValidateConstraint
!  *
!  * Implements the ALTER DOMAIN .. VALIDATE CONSTRAINT statement.
!  */
! void
! AlterDomainValidateConstraint(List *names, char *constrName)
! {
! 	TypeName   *typename;
! 	Oid			domainoid;
! 	Relation	typrel;
! 	Relation	conrel;
! 	HeapTuple	tup;
! 	Form_pg_type typTup;
! 	Form_pg_constraint con;
! 	Form_pg_constraint copy_con;
! 	char	   *conbin;
! 	SysScanDesc	scan;
! 	Datum		val;
! 	bool		found = false;
! 	bool		isnull;
! 	HeapTuple	tuple;
! 	HeapTuple	copyTuple;
! 	ScanKeyData	key;
! 
! 	/* Make a TypeName so we can use standard type lookup machinery */
! 	typename = makeTypeNameFromNameList(names);
! 	domainoid = typenameTypeId(NULL, typename);
! 
! 	/* Look up the domain in the type table */
! 	typrel = heap_open(TypeRelationId, AccessShareLock);
! 
! 	tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainoid));
! 	if (!HeapTupleIsValid(tup))
! 		elog(ERROR, "cache lookup failed for type %u", domainoid);
! 	typTup = (Form_pg_type) GETSTRUCT(tup);
! 
! 	/* Check it's a domain and check user has permission for ALTER DOMAIN */
! 	checkDomainOwner(tup);
! 
! 	/*
! 	 * Find and check the target constraint
! 	 */
! 	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
! 	ScanKeyInit(&key,
! 				Anum_pg_constraint_contypid,
! 				BTEqualStrategyNumber, F_OIDEQ,
! 				ObjectIdGetDatum(domainoid));
! 	scan = systable_beginscan(conrel, ConstraintTypidIndexId,
! 							  true, SnapshotNow, 1, &key);
! 
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		con = (Form_pg_constraint) GETSTRUCT(tuple);
! 		if (strcmp(NameStr(con->conname), constrName) == 0)
! 		{
! 			found = true;
! 			break;
! 		}
! 	}
! 
! 	if (!found)
! 	{
! 		con = NULL;		/* keep compiler quiet */
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" of domain \"%s\" does not exist",
! 						constrName, NameStr(con->conname))));
! 	}
! 
! 	if (con->contype != CONSTRAINT_CHECK)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("constraint \"%s\" of domain \"%s\" is not a check constraint",
! 						constrName, NameStr(con->conname))));
! 
! 	val = SysCacheGetAttr(CONSTROID, tuple,
! 						  Anum_pg_constraint_conbin,
! 						  &isnull);
! 	if (isnull)
! 		elog(ERROR, "null conbin for constraint %u",
! 			 HeapTupleGetOid(tuple));
! 	conbin = TextDatumGetCString(val);
! 
! 	validateDomainConstraint(domainoid, conbin);
! 
! 	/*
! 	 * Now update the catalog, while we have the door open.
! 	 */
! 	copyTuple = heap_copytuple(tuple);
! 	copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
! 	copy_con->convalidated = true;
! 	simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
! 	CatalogUpdateIndexes(conrel, copyTuple);
! 	heap_freetuple(copyTuple);
! 
! 	systable_endscan(scan);
! 
! 	heap_close(typrel, AccessShareLock);
! 	heap_close(conrel, RowExclusiveLock);
! 
! 	ReleaseSysCache(tup);
! }
! 
! static void
! validateDomainConstraint(Oid domainoid, char *ccbin)
! {
! 	Expr	   *expr = (Expr *) stringToNode(ccbin);
! 	List	   *rels;
! 	ListCell   *rt;
! 	EState	   *estate;
! 	ExprContext *econtext;
! 	ExprState  *exprstate;
  
  	/* Need an EState to run ExecEvalExpr */
  	estate = CreateExecutorState();
***************
*** 2092,2102 **** AlterDomainAddConstraint(List *names, Node *newConstraint)
  	}
  
  	FreeExecutorState(estate);
- 
- 	/* Clean up */
- 	heap_close(typrel, RowExclusiveLock);
  }
- 
  /*
   * get_rels_with_domain
   *
--- 2206,2212 ----
***************
*** 2416,2422 **** domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
  						  CONSTRAINT_CHECK,		/* Constraint Type */
  						  false,	/* Is Deferrable */
  						  false,	/* Is Deferred */
! 						  true, /* Is Validated */
  						  InvalidOid,	/* not a relation constraint */
  						  NULL,
  						  0,
--- 2526,2532 ----
  						  CONSTRAINT_CHECK,		/* Constraint Type */
  						  false,	/* Is Deferrable */
  						  false,	/* Is Deferred */
! 						  !constr->skip_validation, /* Is Validated */
  						  InvalidOid,	/* not a relation constraint */
  						  NULL,
  						  0,
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 552,558 **** get_relation_data_width(Oid relid, int32 *attr_widths)
  /*
   * get_relation_constraints
   *
!  * Retrieve the CHECK constraint expressions of the given relation.
   *
   * Returns a List (possibly empty) of constraint expressions.  Each one
   * has been canonicalized, and its Vars are changed to have the varno
--- 552,558 ----
  /*
   * get_relation_constraints
   *
!  * Retrieve the validated CHECK constraint expressions of the given relation.
   *
   * Returns a List (possibly empty) of constraint expressions.  Each one
   * has been canonicalized, and its Vars are changed to have the varno
***************
*** 591,596 **** get_relation_constraints(PlannerInfo *root,
--- 591,603 ----
  		{
  			Node	   *cexpr;
  
+ 			/*
+ 			 * If this constraint hasn't been fully validated yet, we must
+ 			 * ignore it here.
+ 			 */
+ 			if (!constr->check[i].ccvalid)
+ 				continue;
+ 
  			cexpr = stringToNode(constr->check[i].ccbin);
  
  			/*
***************
*** 663,669 **** get_relation_constraints(PlannerInfo *root,
   *
   * Detect whether the relation need not be scanned because it has either
   * self-inconsistent restrictions, or restrictions inconsistent with the
!  * relation's CHECK constraints.
   *
   * Note: this examines only rel->relid, rel->reloptkind, and
   * rel->baserestrictinfo; therefore it can be called before filling in
--- 670,676 ----
   *
   * Detect whether the relation need not be scanned because it has either
   * self-inconsistent restrictions, or restrictions inconsistent with the
!  * relation's validated CHECK constraints.
   *
   * Note: this examines only rel->relid, rel->reloptkind, and
   * rel->baserestrictinfo; therefore it can be called before filling in
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 2746,2751 **** ConstraintElem:
--- 2746,2753 ----
  					n->location = @1;
  					n->raw_expr = $3;
  					n->cooked_expr = NULL;
+ 					n->skip_validation = false;
+ 					n->initially_valid = true;
  					if ($5 != 0)
  						ereport(ERROR,
  								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
***************
*** 2753,2758 **** ConstraintElem:
--- 2755,2771 ----
  								 parser_errposition(@5)));
  					$$ = (Node *)n;
  				}
+ 			| CHECK '(' a_expr ')' NOT VALID
+ 				{
+ 					Constraint *n = makeNode(Constraint);
+ 					n->contype = CONSTR_CHECK;
+ 					n->location = @1;
+ 					n->raw_expr = $3;
+ 					n->cooked_expr = NULL;
+ 					n->skip_validation = true;
+ 					n->initially_valid = false;
+ 					$$ = (Node *)n;
+ 				}
  			| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
  				ConstraintAttributeSpec
  				{
***************
*** 7563,7568 **** AlterDomainStmt:
--- 7576,7590 ----
  					n->behavior = $7;
  					$$ = (Node *)n;
  				}
+ 			/* ALTER DOMAIN <domain> VALIDATE CONSTRAINT <name> */
+ 			| ALTER DOMAIN_P any_name VALIDATE CONSTRAINT name
+ 				{
+ 					AlterDomainStmt *n = makeNode(AlterDomainStmt);
+ 					n->subtype = 'V';
+ 					n->typeName = $3;
+ 					n->name = $6;
+ 					$$ = (Node *)n;
+ 				}
  			;
  
  opt_as:		AS										{}
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 820,825 **** standard_ProcessUtility(Node *parsetree,
--- 820,829 ----
  												  stmt->name,
  												  stmt->behavior);
  						break;
+ 					case 'V':	/* VALIDATE CONSTRAINT */
+ 						AlterDomainValidateConstraint(stmt->typeName,
+ 													  stmt->name);
+ 						break;
  					default:	/* oops */
  						elog(ERROR, "unrecognized alter domain type: %d",
  							 (int) stmt->subtype);
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 3251,3256 **** CheckConstraintFetch(Relation relation)
--- 3251,3257 ----
  			elog(ERROR, "unexpected constraint record found for rel %s",
  				 RelationGetRelationName(relation));
  
+ 		check[found].ccvalid = conform->convalidated;
  		check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
  												  NameStr(conform->conname));
  
*** a/src/include/access/tupdesc.h
--- b/src/include/access/tupdesc.h
***************
*** 29,34 **** typedef struct constrCheck
--- 29,35 ----
  {
  	char	   *ccname;
  	char	   *ccbin;			/* nodeToString representation of expr */
+ 	bool		ccvalid;
  } ConstrCheck;
  
  /* This structure contains constraints of a tuple */
*** a/src/include/catalog/heap.h
--- b/src/include/catalog/heap.h
***************
*** 30,35 **** typedef struct CookedConstraint
--- 30,36 ----
  	char	   *name;			/* name, or NULL if none */
  	AttrNumber	attnum;			/* which attr (only for DEFAULT) */
  	Node	   *expr;			/* transformed default or check expr */
+ 	bool		skip_validation;	/* skip validation? (only for CHECK) */
  	bool		is_local;		/* constraint has local (non-inherited) def */
  	int			inhcount;		/* number of times constraint is inherited */
  } CookedConstraint;
*** a/src/include/commands/typecmds.h
--- b/src/include/commands/typecmds.h
***************
*** 31,36 **** extern Oid	AssignTypeArrayOid(void);
--- 31,37 ----
  extern void AlterDomainDefault(List *names, Node *defaultRaw);
  extern void AlterDomainNotNull(List *names, bool notNull);
  extern void AlterDomainAddConstraint(List *names, Node *constr);
+ extern void AlterDomainValidateConstraint(List *names, char *constrName);
  extern void AlterDomainDropConstraint(List *names, const char *constrName,
  						  DropBehavior behavior);
  
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1190,1195 **** typedef enum AlterTableType
--- 1190,1196 ----
  	AT_AddConstraint,			/* add constraint */
  	AT_AddConstraintRecurse,	/* internal to commands/tablecmds.c */
  	AT_ValidateConstraint,		/* validate constraint */
+ 	AT_ValidateConstraintRecurse, /* internal to commands/tablecmds.c */
  	AT_ProcessedConstraint,		/* pre-processed add constraint (local in
  								 * parser/parse_utilcmd.c) */
  	AT_AddIndexConstraint,		/* add constraint using existing index */
***************
*** 1543,1548 **** typedef struct Constraint
--- 1544,1551 ----
  	char		fk_matchtype;	/* FULL, PARTIAL, UNSPECIFIED */
  	char		fk_upd_action;	/* ON UPDATE action */
  	char		fk_del_action;	/* ON DELETE action */
+ 
+ 	/* Fields used for constraints that allow a NOT VALID specification */
  	bool		skip_validation;	/* skip validation of existing rows? */
  	bool		initially_valid;	/* start the new constraint as valid */
  } Constraint;
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 196,209 **** DELETE FROM tmp3 where a=5;
--- 196,310 ----
  -- 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 a non-verified CHECK constraint
+ ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10); -- fail
+ ERROR:  check constraint "b_greater_than_ten" is violated by some row
+ ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10) NOT VALID; -- succeeds
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- fails
+ ERROR:  check constraint "b_greater_than_ten" is violated by some row
+ DELETE FROM tmp3 WHERE NOT b > 10;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
+ -- Test inherited NOT VALID CHECK constraints
+ select * from tmp3;
+  a | b  
+ ---+----
+  1 | 20
+ (1 row)
+ 
+ CREATE TABLE tmp6 () INHERITS (tmp3);
+ CREATE TABLE tmp7 () INHERITS (tmp3);
+ INSERT INTO tmp6 VALUES (6, 30), (7, 16);
+ ALTER TABLE tmp3 ADD CONSTRAINT b_le_20 CHECK (b <= 20) NOT VALID;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- fails
+ ERROR:  check constraint "b_le_20" is violated by some row
+ DELETE FROM tmp6 WHERE b > 20;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- succeeds
+ -- An already validated constraint must not be revalidated
+ CREATE FUNCTION boo(int) RETURNS int IMMUTABLE STRICT LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE 'boo: %', $1; RETURN $1; END; $$;
+ INSERT INTO tmp7 VALUES (8, 18);
+ ALTER TABLE tmp7 ADD CONSTRAINT identity CHECK (b = boo(b));
+ NOTICE:  boo: 18
+ ALTER TABLE tmp3 ADD CONSTRAINT IDENTITY check (b = boo(b)) NOT VALID;
+ NOTICE:  merging constraint "identity" with inherited definition
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT identity;
+ NOTICE:  boo: 16
+ NOTICE:  boo: 20
  -- 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;
  ERROR:  there is no unique constraint matching given keys for referenced table "tmp4"
+ DROP TABLE tmp7;
+ DROP TABLE tmp6;
  DROP TABLE tmp5;
  DROP TABLE tmp4;
  DROP TABLE tmp3;
  DROP TABLE tmp2;
+ -- NOT VALID with plan invalidation -- ensure we don't use a constraint for
+ -- exclusion until validated
+ set constraint_exclusion TO 'partition';
+ create table nv_parent (d date); 
+ create table nv_child_2010 () inherits (nv_parent);
+ create table nv_child_2011 () inherits (nv_parent);
+ alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid;
+ alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid;
+ explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31';
+                                    QUERY PLAN                                    
+ ---------------------------------------------------------------------------------
+  Result
+    ->  Append
+          ->  Seq Scan on nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+          ->  Seq Scan on nv_child_2010 nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+          ->  Seq Scan on nv_child_2011 nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ (8 rows)
+ 
+ create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent);
+ explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date;
+                                    QUERY PLAN                                    
+ ---------------------------------------------------------------------------------
+  Result
+    ->  Append
+          ->  Seq Scan on nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+          ->  Seq Scan on nv_child_2010 nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+          ->  Seq Scan on nv_child_2011 nv_parent
+                Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
+ (8 rows)
+ 
+ explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
+                                    QUERY PLAN                                    
+ ---------------------------------------------------------------------------------
+  Result
+    ->  Append
+          ->  Seq Scan on nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+          ->  Seq Scan on nv_child_2010 nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+          ->  Seq Scan on nv_child_2011 nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+          ->  Seq Scan on nv_child_2009 nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ (10 rows)
+ 
+ -- after validation, the constraint should be used
+ alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check;
+ explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
+                                    QUERY PLAN                                    
+ ---------------------------------------------------------------------------------
+  Result
+    ->  Append
+          ->  Seq Scan on nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+          ->  Seq Scan on nv_child_2010 nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+          ->  Seq Scan on nv_child_2009 nv_parent
+                Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
+ (8 rows)
+ 
  -- Foreign key adding test with mixed types
  -- Note: these tables are TEMP to avoid name conflicts when this test
  -- is run in parallel with foreign_key.sql.
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
***************
*** 352,357 **** alter domain con drop constraint t;
--- 352,368 ----
  insert into domcontest values (-5); --fails
  ERROR:  value for domain con violates check constraint "con_check"
  insert into domcontest values (42);
+ -- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
+ create domain things AS INT;
+ CREATE TABLE thethings (stuff things);
+ INSERT INTO thethings (stuff) VALUES (55);
+ ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11);
+ ERROR:  column "stuff" of table "thethings" contains values that violate the new constraint
+ ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11) NOT VALID;
+ ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+ ERROR:  column "stuff" of table "thethings" contains values that violate the new constraint
+ UPDATE thethings SET stuff = 10;
+ ALTER DOMAIN things VALIDATE CONSTRAINT meow;
  -- Confirm ALTER DOMAIN with RULES.
  create table domtab (col1 integer);
  create domain dom as integer;
*** a/src/test/regress/sql/alter_table.sql
--- b/src/test/regress/sql/alter_table.sql
***************
*** 236,247 **** DELETE FROM tmp3 where a=5;
--- 236,276 ----
  ALTER TABLE tmp3 validate constraint tmpconstr;
  ALTER TABLE tmp3 validate constraint tmpconstr;
  
+ -- Try a non-verified CHECK constraint
+ ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10); -- fail
+ ALTER TABLE tmp3 ADD CONSTRAINT b_greater_than_ten CHECK (b > 10) NOT VALID; -- succeeds
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- fails
+ DELETE FROM tmp3 WHERE NOT b > 10;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_greater_than_ten; -- succeeds
+ 
+ -- Test inherited NOT VALID CHECK constraints
+ select * from tmp3;
+ CREATE TABLE tmp6 () INHERITS (tmp3);
+ CREATE TABLE tmp7 () INHERITS (tmp3);
+ 
+ INSERT INTO tmp6 VALUES (6, 30), (7, 16);
+ ALTER TABLE tmp3 ADD CONSTRAINT b_le_20 CHECK (b <= 20) NOT VALID;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- fails
+ DELETE FROM tmp6 WHERE b > 20;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT b_le_20;	-- succeeds
+ 
+ -- An already validated constraint must not be revalidated
+ CREATE FUNCTION boo(int) RETURNS int IMMUTABLE STRICT LANGUAGE plpgsql AS $$ BEGIN RAISE NOTICE 'boo: %', $1; RETURN $1; END; $$;
+ INSERT INTO tmp7 VALUES (8, 18);
+ ALTER TABLE tmp7 ADD CONSTRAINT identity CHECK (b = boo(b));
+ ALTER TABLE tmp3 ADD CONSTRAINT IDENTITY check (b = boo(b)) NOT VALID;
+ ALTER TABLE tmp3 VALIDATE CONSTRAINT identity;
  
  -- 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;
  
+ DROP TABLE tmp7;
+ 
+ DROP TABLE tmp6;
+ 
  DROP TABLE tmp5;
  
  DROP TABLE tmp4;
***************
*** 250,255 **** DROP TABLE tmp3;
--- 279,301 ----
  
  DROP TABLE tmp2;
  
+ -- NOT VALID with plan invalidation -- ensure we don't use a constraint for
+ -- exclusion until validated
+ set constraint_exclusion TO 'partition';
+ create table nv_parent (d date); 
+ create table nv_child_2010 () inherits (nv_parent);
+ create table nv_child_2011 () inherits (nv_parent);
+ alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid;
+ alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid;
+ explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31';
+ create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent);
+ explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date;
+ explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
+ -- after validation, the constraint should be used
+ alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check;
+ explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
+ 
+ 
  -- Foreign key adding test with mixed types
  
  -- Note: these tables are TEMP to avoid name conflicts when this test
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
***************
*** 259,264 **** alter domain con drop constraint t;
--- 259,274 ----
  insert into domcontest values (-5); --fails
  insert into domcontest values (42);
  
+ -- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
+ create domain things AS INT;
+ CREATE TABLE thethings (stuff things);
+ INSERT INTO thethings (stuff) VALUES (55);
+ ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11);
+ ALTER DOMAIN things ADD CONSTRAINT meow CHECK (VALUE < 11) NOT VALID;
+ ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+ UPDATE thethings SET stuff = 10;
+ ALTER DOMAIN things VALIDATE CONSTRAINT meow;
+ 
  -- Confirm ALTER DOMAIN with RULES.
  create table domtab (col1 integer);
  create domain dom as integer;
