ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
						 bool recursing, LOCKMODE lockmode)
{
	Relation	conrel;
	SysScanDesc scan;
	ScanKeyData key;
	HeapTuple	tuple;
	Form_pg_constraint con = NULL;
	bool		found = false;

	conrel = heap_open(ConstraintRelationId, RowExclusiveLock);

	/*
	 * Find and check the target constraint
	 */
	ScanKeyInit(&key,
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(rel)));
	scan = systable_beginscan(conrel, ConstraintRelidIndexId,
							  true, NULL, 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)
		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(constrName, rel, refrel,
										 con->conindid,
										 conid);
			heap_close(refrel, NoLock);

			/*
			 * Foreign keys do not inherit, so we purposely 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);

		InvokeObjectPostAlterHook(ConstraintRelationId,
								  HeapTupleGetOid(tuple), 0);

		heap_freetuple(copyTuple);
	}

	systable_endscan(scan);

	heap_close(conrel, RowExclusiveLock);
}