From 8f812fe14e4d5d133a4f6d060cc3021a84a577f4 Mon Sep 17 00:00:00 2001
From: Himanshu Upadhyaya <himanshu.upadhyaya@enterprisedb.com>
Date: Mon, 2 Oct 2023 17:45:53 +0530
Subject: [PATCH v3] Implementation of "CHECK Constraint" to make it

Deferrable.
---
 src/backend/access/common/tupdesc.c       |   6 +-
 src/backend/catalog/heap.c                |  73 ++++++++---
 src/backend/commands/constraint.c         | 116 ++++++++++++++++++
 src/backend/commands/copyfrom.c           |  10 +-
 src/backend/commands/tablecmds.c          |  19 ++-
 src/backend/commands/trigger.c            |  37 ++++--
 src/backend/executor/execMain.c           |  41 ++++++-
 src/backend/executor/execReplication.c    |  10 +-
 src/backend/executor/nodeModifyTable.c    |  29 +++--
 src/backend/optimizer/util/plancat.c      |   9 +-
 src/backend/parser/gram.y                 |   2 +-
 src/backend/parser/parse_utilcmd.c        |   9 +-
 src/backend/utils/cache/relcache.c        |   2 +
 src/include/access/tupdesc.h              |   2 +
 src/include/catalog/heap.h                |   2 +
 src/include/catalog/pg_proc.dat           |   5 +
 src/include/commands/trigger.h            |   2 +
 src/include/executor/executor.h           |  42 ++++++-
 src/test/regress/expected/constraints.out | 140 ++++++++++++++++++++++
 src/test/regress/sql/constraints.sql      | 133 ++++++++++++++++++++
 src/tools/pgindent/typedefs.list          |   1 +
 21 files changed, 625 insertions(+), 65 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index ce2c7bce85..5ff488b9e7 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -204,6 +204,8 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 				cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
 				cpy->check[i].ccvalid = constr->check[i].ccvalid;
 				cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
+				cpy->check[i].ccdeferrable = constr->check[i].ccdeferrable;
+				cpy->check[i].ccdeferred = constr->check[i].ccdeferred;
 			}
 		}
 
@@ -531,7 +533,9 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			if (!(strcmp(check1->ccname, check2->ccname) == 0 &&
 				  strcmp(check1->ccbin, check2->ccbin) == 0 &&
 				  check1->ccvalid == check2->ccvalid &&
-				  check1->ccnoinherit == check2->ccnoinherit))
+				  check1->ccnoinherit == check2->ccnoinherit &&
+				  check1->ccdeferrable == check2->ccdeferrable &&
+				  check1->ccdeferred == check2->ccdeferred))
 				return false;
 		}
 	}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d03c961678..5852428946 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -52,10 +52,12 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_tablespace.h"
+#include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
+#include "commands/trigger.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
@@ -63,6 +65,7 @@
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
+#include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "pgstat.h"
@@ -101,12 +104,14 @@ static ObjectAddress AddNewRelationType(const char *typeName,
 										Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
 static Oid	StoreRelCheck(Relation rel, const char *ccname, Node *expr,
+						  bool is_deferrable, bool initdeferred,
 						  bool is_validated, bool is_local, int inhcount,
-						  bool is_no_inherit, bool is_internal);
+						  bool is_no_inherit, bool is_internal, int numchecks);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
 							 bool is_internal);
 static bool MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
 										bool allow_merge, bool is_local,
+										bool is_deferrable, bool is_deferred,
 										bool is_initially_valid,
 										bool is_no_inherit);
 static void SetRelationNumChecks(Relation rel, int numchecks);
@@ -2049,8 +2054,9 @@ SetAttrMissing(Oid relid, char *attname, char *value)
  */
 static Oid
 StoreRelCheck(Relation rel, const char *ccname, Node *expr,
+			  bool is_deferrable, bool initdeferred,
 			  bool is_validated, bool is_local, int inhcount,
-			  bool is_no_inherit, bool is_internal)
+			  bool is_no_inherit, bool is_internal, int numchecks)
 {
 	char	   *ccbin;
 	List	   *varList;
@@ -2113,8 +2119,10 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 		CreateConstraintEntry(ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
-							  false,	/* Is Deferrable */
-							  false,	/* Is Deferred */
+							  is_deferrable,	/* Is Check Constraint
+												 * deferrable */
+							  initdeferred, /* Is Check Constraint initially
+											 * deferred */
 							  is_validated,
 							  InvalidOid,	/* no parent constraint */
 							  RelationGetRelid(rel),	/* relation */
@@ -2141,6 +2149,38 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
 							  is_internal); /* internally constructed? */
+	SetRelationNumChecks(rel, numchecks);
+	CommandCounterIncrement();
+
+	/*
+	 * If the constraint is deferrable, create the deferred trigger to
+	 * re-validate the check constraint.(The trigger will be given an internal
+	 * dependency on the constraint by CreateTrigger, so there's no need to do
+	 * anything more here.)
+	 */
+	if (is_deferrable)
+	{
+		CreateTrigStmt *trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
+		trigger->trigname = "Check_ConstraintTrigger";
+		trigger->relation = NULL;
+		trigger->funcname = SystemFuncName("check_constraint_recheck");
+		trigger->args = NIL;
+		trigger->row = true;
+		trigger->timing = TRIGGER_TYPE_AFTER;
+		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
+		trigger->columns = NIL;
+		trigger->whenClause = NULL;
+		trigger->transitionRels = NIL;
+		trigger->deferrable = true;
+		trigger->initdeferred = initdeferred;
+		trigger->constrrel = NULL;
+
+		(void) CreateTrigger(trigger, NULL, RelationGetRelid(rel),
+							 InvalidOid, constrOid, InvalidOid, InvalidOid,
+							 InvalidOid, NULL, true, false);
+	}
 
 	pfree(ccbin);
 
@@ -2233,10 +2273,10 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 			case CONSTR_CHECK:
 				con->conoid =
 					StoreRelCheck(rel, con->name, con->expr,
+								  con->is_deferrable, con->is_deferred,
 								  !con->skip_validation, con->is_local,
 								  con->inhcount, con->is_no_inherit,
-								  is_internal);
-				numchecks++;
+								  is_internal, ++numchecks);
 				break;
 
 			case CONSTR_NOTNULL:
@@ -2251,9 +2291,6 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 					 (int) con->contype);
 		}
 	}
-
-	if (numchecks > 0)
-		SetRelationNumChecks(rel, numchecks);
 }
 
 /*
@@ -2451,6 +2488,8 @@ AddRelationNewConstraints(Relation rel,
 				 */
 				if (MergeWithExistingConstraint(rel, ccname, expr,
 												allow_merge, is_local,
+												cdef->deferrable,
+												cdef->initdeferred,
 												cdef->initially_valid,
 												cdef->is_no_inherit))
 					continue;
@@ -2499,10 +2538,10 @@ AddRelationNewConstraints(Relation rel,
 			 * OK, store it.
 			 */
 			constrOid =
-				StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
-							  is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
-
-			numchecks++;
+				StoreRelCheck(rel, ccname, expr, cdef->deferrable,
+							  cdef->initdeferred, cdef->initially_valid,
+							  is_local, is_local ? 0 : 1, cdef->is_no_inherit,
+							  is_internal, ++numchecks);
 
 			cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 			cooked->contype = CONSTR_CHECK;
@@ -2606,8 +2645,8 @@ AddRelationNewConstraints(Relation rel,
 static bool
 MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
 							bool allow_merge, bool is_local,
-							bool is_initially_valid,
-							bool is_no_inherit)
+							bool is_deferrable, bool is_deferred,
+							bool is_initially_valid, bool is_no_inherit)
 {
 	bool		found;
 	Relation	conDesc;
@@ -2653,7 +2692,9 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
 			if (isnull)
 				elog(ERROR, "null conbin for rel %s",
 					 RelationGetRelationName(rel));
-			if (equal(expr, stringToNode(TextDatumGetCString(val))))
+			if (equal(expr, stringToNode(TextDatumGetCString(val))) &&
+				con->condeferrable == is_deferrable &&
+				con->condeferred == is_deferred)
 				found = true;
 		}
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 35c4451fc0..debf33be26 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -203,3 +203,119 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 
 	return PointerGetDatum(NULL);
 }
+
+/*
+ *  check_constraint_recheck- trigger function to do a deferred check for CHECK constraint.
+ *
+ * This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
+ * for any rows recorded as potential violation of Deferred check
+ * constraint.
+ *
+ * This may be an end-of-statement check or a commit-time check.
+ */
+Datum
+check_constraint_recheck(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	const char *funcname = "check_constraint_recheck";
+	ItemPointerData checktid;
+	Relation	rel;
+	EState	   *estate;
+	TupleTableSlot *slot;
+	ResultRelInfo *rInfo = NULL;
+	TableScanDesc scan;
+	ExprContext *econtext;
+
+	/*
+	 * Make sure this is being called as an AFTER ROW trigger.  Note:
+	 * translatable error strings are shared with ri_triggers.c, so resist the
+	 * temptation to fold the function name into them.
+	 */
+	if (!CALLED_AS_TRIGGER(fcinfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+				 errmsg("function \"%s\" was not called by trigger manager",
+						funcname)));
+
+	if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
+		!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+				 errmsg("function \"%s\" must be fired AFTER ROW",
+						funcname)));
+
+	/*
+	 * Get the new data that was inserted/updated.
+	 */
+	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+		checktid = trigdata->tg_trigslot->tts_tid;
+	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+		checktid = trigdata->tg_newslot->tts_tid;
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+				 errmsg("function \"%s\" must be fired for INSERT or UPDATE",
+						funcname)));
+		ItemPointerSetInvalid(&checktid);	/* keep compiler quiet */
+	}
+
+	slot = table_slot_create(trigdata->tg_relation, NULL);
+	scan = table_beginscan_tid(trigdata->tg_relation, SnapshotSelf);
+
+	/*
+	 * Now look for latest tuple in that chain because it is possible that
+	 * same tuple is updated(or even inserted and then updated/deleted)
+	 * multiple times in a transaction.
+	 */
+	heap_get_latest_tid(scan, &checktid);
+
+	/*
+	 * Check if latest tuple is visible to current transaction.
+	 * heap_get_latest_tid(as called above) provides the latest tuple as per
+	 * current Snapshot and if tuple is not visible (if
+	 * table_tuple_fetch_row_version returns false), it means tuple is
+	 * inserted/updated and then deleted in the same transaction. We are sure
+	 * that initially tuple was inserted or or updated in this transaction
+	 * because this constraint trigger function was called as an UPDATE or
+	 * INSERT event of after row trigger.
+	 */
+	if (!table_tuple_fetch_row_version(trigdata->tg_relation,
+									   &checktid,
+									   SnapshotSelf,
+									   slot))
+	{
+		table_endscan(scan);
+		ExecDropSingleTupleTableSlot(slot);
+		return PointerGetDatum(NULL);
+	}
+
+	/* Make a local estate and Exprcontext */
+	estate = CreateExecutorState();
+	econtext = GetPerTupleExprContext(estate);
+	econtext->ecxt_scantuple = slot;
+
+	/*
+	 * Open the relation, acquiring a AccessShareLock.
+	 */
+	rel = table_open(trigdata->tg_relation->rd_id, AccessShareLock);
+	rInfo = ExecGetTriggerResultRel(estate, RelationGetRelid(rel),
+									NULL);
+	ExecConstraints(rInfo, slot, estate, CHECK_DEFERRED_EXISTING);
+
+	/*
+	 * If that worked, then this potential failure of check constraint is now
+	 * resolved, and we are done.
+	 */
+	if (estate != NULL)
+	{
+		ExecCloseResultRelations(estate);
+		ExecResetTupleTable(estate->es_tupleTable, false);
+		FreeExecutorState(estate);
+	}
+
+	table_endscan(scan);
+	ExecDropSingleTupleTableSlot(slot);
+	table_close(rel, AccessShareLock);
+	return PointerGetDatum(NULL);
+}
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 70871ed819..36d2c5f80b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -375,7 +375,7 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 					slot->tts_tableOid = relid;
 
 					ExecARInsertTriggers(estate, resultRelInfo,
-										 slot, NIL,
+										 slot, NIL, InvalidOid,
 										 cstate->transition_capture);
 				}
 			}
@@ -437,7 +437,7 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 										  buffer->slots[i], estate, false,
 										  false, NULL, NIL, false);
 				ExecARInsertTriggers(estate, resultRelInfo,
-									 slots[i], recheckIndexes,
+									 slots[i], recheckIndexes, InvalidOid,
 									 cstate->transition_capture);
 				list_free(recheckIndexes);
 			}
@@ -452,7 +452,7 @@ CopyMultiInsertBufferFlush(CopyMultiInsertInfo *miinfo,
 			{
 				cstate->cur_lineno = buffer->linenos[i];
 				ExecARInsertTriggers(estate, resultRelInfo,
-									 slots[i], NIL,
+									 slots[i], NIL, InvalidOid,
 									 cstate->transition_capture);
 			}
 
@@ -1169,7 +1169,7 @@ CopyFrom(CopyFromState cstate)
 				 */
 				if (resultRelInfo->ri_FdwRoutine == NULL &&
 					resultRelInfo->ri_RelationDesc->rd_att->constr)
-					ExecConstraints(resultRelInfo, myslot, estate);
+					ExecConstraints(resultRelInfo, myslot, estate, CHECK_DEFERRED_NO);
 
 				/*
 				 * Also check the tuple against the partition constraint, if
@@ -1254,7 +1254,7 @@ CopyFrom(CopyFromState cstate)
 
 					/* AFTER ROW INSERT Triggers */
 					ExecARInsertTriggers(estate, resultRelInfo, myslot,
-										 recheckIndexes, cstate->transition_capture);
+										 recheckIndexes, InvalidOid, cstate->transition_capture);
 
 					list_free(recheckIndexes);
 				}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 73b8dea81c..afe068531b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -353,7 +353,8 @@ static void RangeVarCallbackForTruncate(const RangeVar *relation,
 static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
-static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
+static List *MergeCheckConstraint(List *constraints, const char *name,
+								  Node *expr, bool is_deferrable, bool is_deferred);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -933,6 +934,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cooked->name = NULL;
 			cooked->attnum = attnum;
 			cooked->expr = colDef->cooked_default;
+			cooked->is_deferrable = false;	/* By default constraint is not
+											 * deferrable */
+			cooked->is_deferred = false;	/* ditto */
 			cooked->skip_validation = false;
 			cooked->is_local = true;	/* not used for defaults */
 			cooked->inhcount = 0;	/* ditto */
@@ -2871,6 +2875,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			for (int i = 0; i < constr->num_check; i++)
 			{
 				char	   *name = check[i].ccname;
+				bool		is_deferrable = check[i].ccdeferrable;
+				bool		is_deferred = check[i].ccdeferred;
 				Node	   *expr;
 				bool		found_whole_row;
 
@@ -2897,7 +2903,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 									   name,
 									   RelationGetRelationName(relation))));
 
-				constraints = MergeCheckConstraint(constraints, name, expr);
+				constraints = MergeCheckConstraint(constraints, name, expr,
+												   is_deferrable, is_deferred);
 			}
 		}
 
@@ -3251,7 +3258,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
  * the list.
  */
 static List *
-MergeCheckConstraint(List *constraints, const char *name, Node *expr)
+MergeCheckConstraint(List *constraints, const char *name, Node *expr,
+					 bool is_deferrable, bool is_deferred)
 {
 	ListCell   *lc;
 	CookedConstraint *newcon;
@@ -3291,6 +3299,8 @@ MergeCheckConstraint(List *constraints, const char *name, Node *expr)
 	newcon->contype = CONSTR_CHECK;
 	newcon->name = pstrdup(name);
 	newcon->expr = expr;
+	newcon->is_deferrable = is_deferrable;
+	newcon->is_deferred = is_deferred;
 	newcon->inhcount = 1;
 	return lappend(constraints, newcon);
 }
@@ -19491,6 +19501,9 @@ DetachAddConstraintIfNeeded(List **wqueue, Relation partRel)
 		n->raw_expr = NULL;
 		n->cooked_expr = nodeToString(make_ands_explicit(constraintExpr));
 		n->initially_valid = true;
+		n->deferrable = false;	/* By default this new constraint must be
+								 * non-deferrable */
+		n->initdeferred = false;	/* Ditto */
 		n->skip_validation = true;
 		/* It's a re-add, since it nominally already exists */
 		ATAddCheckNNConstraint(wqueue, tab, partRel, n,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 52177759ab..35915eb7f3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -103,7 +103,8 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 								  ResultRelInfo *dst_partinfo,
 								  int event, bool row_trigger,
 								  TupleTableSlot *oldslot, TupleTableSlot *newslot,
-								  List *recheckIndexes, Bitmapset *modifiedCols,
+								  List *recheckIndexes, Oid recheckConstraints,
+								  Bitmapset *modifiedCols,
 								  TransitionCaptureState *transition_capture,
 								  bool is_crosspart_update);
 static void AfterTriggerEnlargeQueryState(void);
@@ -2456,7 +2457,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 	if (trigdesc && trigdesc->trig_insert_after_statement)
 		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_INSERT,
-							  false, NULL, NULL, NIL, NULL, transition_capture,
+							  false, NULL, NULL, NIL, InvalidOid, NULL, transition_capture,
 							  false);
 }
 
@@ -2539,7 +2540,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 void
 ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot, List *recheckIndexes,
-					 TransitionCaptureState *transition_capture)
+					 Oid recheckConstraints, TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
@@ -2548,7 +2549,9 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_INSERT,
 							  true, NULL, slot,
-							  recheckIndexes, NULL,
+							  recheckIndexes,
+							  recheckConstraints,
+							  NULL,
 							  transition_capture,
 							  false);
 }
@@ -2674,7 +2677,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 	if (trigdesc && trigdesc->trig_delete_after_statement)
 		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_DELETE,
-							  false, NULL, NULL, NIL, NULL, transition_capture,
+							  false, NULL, NULL, NIL, InvalidOid, NULL, transition_capture,
 							  false);
 }
 
@@ -2807,7 +2810,7 @@ ExecARDeleteTriggers(EState *estate,
 
 		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_DELETE,
-							  true, slot, NULL, NIL, NULL,
+							  true, slot, NULL, NIL, InvalidOid, NULL,
 							  transition_capture,
 							  is_crosspart_update);
 	}
@@ -2930,7 +2933,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 	if (trigdesc && trigdesc->trig_update_after_statement)
 		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_UPDATE,
-							  false, NULL, NULL, NIL,
+							  false, NULL, NULL, NIL, InvalidOid,
 							  ExecGetAllUpdatedCols(relinfo, estate),
 							  transition_capture,
 							  false);
@@ -3089,6 +3092,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple fdw_trigtuple,
 					 TupleTableSlot *newslot,
 					 List *recheckIndexes,
+					 Oid recheckConstraints,
 					 TransitionCaptureState *transition_capture,
 					 bool is_crosspart_update)
 {
@@ -3133,7 +3137,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 							  src_partinfo, dst_partinfo,
 							  TRIGGER_EVENT_UPDATE,
 							  true,
-							  oldslot, newslot, recheckIndexes,
+							  oldslot, newslot, recheckIndexes, recheckConstraints,
 							  ExecGetAllUpdatedCols(relinfo, estate),
 							  transition_capture,
 							  is_crosspart_update);
@@ -3262,7 +3266,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
 		AfterTriggerSaveEvent(estate, relinfo,
 							  NULL, NULL,
 							  TRIGGER_EVENT_TRUNCATE,
-							  false, NULL, NULL, NIL, NULL, NULL,
+							  false, NULL, NULL, NIL, InvalidOid, NULL, NULL,
 							  false);
 }
 
@@ -6051,7 +6055,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 					  ResultRelInfo *dst_partinfo,
 					  int event, bool row_trigger,
 					  TupleTableSlot *oldslot, TupleTableSlot *newslot,
-					  List *recheckIndexes, Bitmapset *modifiedCols,
+					  List *recheckIndexes, Oid recheckConstraints,
+					  Bitmapset *modifiedCols,
 					  TransitionCaptureState *transition_capture,
 					  bool is_crosspart_update)
 {
@@ -6389,6 +6394,18 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			}
 		}
 
+		/*
+		 * If the trigger is deferred check constraint recheck trigger, only
+		 * queue it for the constraint that was potentially violated.
+		 */
+		if (trigger->tgfoid == F_CHECK_CONSTRAINT_RECHECK)
+		{
+			if (recheckConstraints != trigger->tgconstraint)
+			{
+				continue;		/* Check constraint not violated */
+			}
+		}
+
 		/*
 		 * If the trigger is a deferred unique constraint check trigger, only
 		 * queue it if the unique constraint was potentially violated, which
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 4c5a7bbf62..47863d5b79 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -45,6 +45,7 @@
 #include "access/xact.h"
 #include "catalog/namespace.h"
 #include "catalog/partition.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_publication.h"
 #include "commands/matview.h"
 #include "commands/trigger.h"
@@ -1734,12 +1735,15 @@ ExecutePlan(EState *estate,
 
 /*
  * ExecRelCheck --- check that tuple meets constraints for result relation
+ * and populate recheckConstraints with Oid of violated deferred constraint
+ * if that constraint is deferrable.
  *
  * Returns NULL if OK, else name of failed check constraint
  */
 static const char *
 ExecRelCheck(ResultRelInfo *resultRelInfo,
-			 TupleTableSlot *slot, EState *estate)
+			 TupleTableSlot *slot, EState *estate,
+			 EnforceDeferredCheck checkConstraint, bool *recheckConstraints)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	int			ncheck = rel->rd_att->constr->num_check;
@@ -1798,7 +1802,20 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 		 * ExecQual.
 		 */
 		if (!ExecCheck(checkconstr, econtext))
+		{
+
+			/*
+			 * If the constraint is deferrable and caller is
+			 * CHECK_DEFERRED_YES then constraints must be revalidated at
+			 * the time of enforcing the constraint, that is at commit time
+			 * and via after Row trigger.
+			 */
+			if (checkConstraint == CHECK_DEFERRED_YES && check[i].ccdeferrable)
+			{
+				*recheckConstraints = true;
+			}
 			return check[i].ccname;
+		}
 	}
 
 	/* NULL result means no error */
@@ -1936,18 +1953,27 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
  * have been converted from the original input tuple after tuple routing.
  * 'resultRelInfo' is the final result relation, after tuple routing.
  */
-void
+Oid
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate)
+				TupleTableSlot *slot, EState *estate,
+				EnforceDeferredCheck checkConstraint)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
 	TupleConstr *constr = tupdesc->constr;
 	Bitmapset  *modifiedCols;
+	bool		recheckConstraints = false;
+	const char *failed;
 
 	Assert(constr);				/* we should not be called otherwise */
 
-	if (constr->has_not_null)
+	/*
+	 * NOT NULL constraint is not supported as deferrable so don't need to
+	 * recheck( CHECK_DEFERRED_EXISTING means it is getting called by trigger
+	 * function check_constraint_recheck for re-checking the potential
+	 * constraint violation of "CHECK" constraint on one/more columns).
+	 */
+	if (constr->has_not_null && checkConstraint != CHECK_DEFERRED_EXISTING)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -2013,9 +2039,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 
 	if (rel->rd_rel->relchecks > 0)
 	{
-		const char *failed;
 
-		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
+		if ((failed = ExecRelCheck(resultRelInfo, slot, estate, checkConstraint, &recheckConstraints)) != NULL
+			&& !recheckConstraints)
 		{
 			char	   *val_desc;
 			Relation	orig_rel = rel;
@@ -2060,6 +2086,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(orig_rel, failed)));
 		}
 	}
+	return (recheckConstraints ?
+			get_relation_constraint_oid(RelationGetRelid(rel), failed, false) :
+			InvalidOid);
 }
 
 /*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 81f27042bc..989baff171 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -515,6 +515,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		Oid			recheckConstraints = false;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -524,7 +525,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
-			ExecConstraints(resultRelInfo, slot, estate);
+			recheckConstraints = ExecConstraints(resultRelInfo, slot, estate, CHECK_DEFERRED_YES);
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
@@ -538,7 +539,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 
 		/* AFTER ROW INSERT Triggers */
 		ExecARInsertTriggers(estate, resultRelInfo, slot,
-							 recheckIndexes, NULL);
+							 recheckIndexes, recheckConstraints, NULL);
 
 		/*
 		 * XXX we should in theory pass a TransitionCaptureState object to the
@@ -583,6 +584,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	{
 		List	   *recheckIndexes = NIL;
 		TU_UpdateIndexes update_indexes;
+		Oid			recheckConstraints = InvalidOid;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -592,7 +594,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 
 		/* Check the constraints of the tuple */
 		if (rel->rd_att->constr)
-			ExecConstraints(resultRelInfo, slot, estate);
+			recheckConstraints = ExecConstraints(resultRelInfo, slot, estate, CHECK_DEFERRED_YES);
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
@@ -609,7 +611,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 NULL, NULL,
 							 tid, NULL, slot,
-							 recheckIndexes, NULL, false);
+							 recheckIndexes, recheckConstraints, NULL, false);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d21a178ad5..133337f9c3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -767,6 +767,7 @@ ExecInsert(ModifyTableContext *context,
 	OnConflictAction onconflict = node->onConflictAction;
 	PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 	MemoryContext oldContext;
+	Oid			recheckConstraints = InvalidOid;
 
 	/*
 	 * If the input result relation is a partitioned table, find the leaf
@@ -995,7 +996,7 @@ ExecInsert(ModifyTableContext *context,
 		 * Check the constraints of the tuple.
 		 */
 		if (resultRelationDesc->rd_att->constr)
-			ExecConstraints(resultRelInfo, slot, estate);
+			recheckConstraints = ExecConstraints(resultRelInfo, slot, estate, CHECK_DEFERRED_YES);
 
 		/*
 		 * Also check the tuple against the partition constraint, if there is
@@ -1162,6 +1163,7 @@ ExecInsert(ModifyTableContext *context,
 							 NULL,
 							 slot,
 							 NULL,
+							 InvalidOid,
 							 mtstate->mt_transition_capture,
 							 false);
 
@@ -1173,7 +1175,7 @@ ExecInsert(ModifyTableContext *context,
 	}
 
 	/* AFTER ROW INSERT Triggers */
-	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes,
+	ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes, recheckConstraints,
 						 ar_insert_trig_tcs);
 
 	list_free(recheckIndexes);
@@ -1247,7 +1249,7 @@ ExecBatchInsert(ModifyTableState *mtstate,
 		slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, slot, NIL,
+		ExecARInsertTriggers(estate, resultRelInfo, slot, NIL, InvalidOid,
 							 mtstate->mt_transition_capture);
 
 		/*
@@ -1380,7 +1382,7 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		ExecARUpdateTriggers(estate, resultRelInfo,
 							 NULL, NULL,
 							 tupleid, oldtuple,
-							 NULL, NULL, mtstate->mt_transition_capture,
+							 NULL, NULL, InvalidOid, mtstate->mt_transition_capture,
 							 false);
 
 		/*
@@ -1967,7 +1969,7 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
 static TM_Result
 ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 			  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
-			  bool canSetTag, UpdateContext *updateCxt)
+			  bool canSetTag, UpdateContext *updateCxt, Oid *recheckConstraints)
 {
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2087,7 +2089,7 @@ lreplace:
 	 * have it validate all remaining checks.
 	 */
 	if (resultRelationDesc->rd_att->constr)
-		ExecConstraints(resultRelInfo, slot, estate);
+		*recheckConstraints = ExecConstraints(resultRelInfo, slot, estate, CHECK_DEFERRED_YES);
 
 	/*
 	 * replace the heap tuple
@@ -2120,7 +2122,7 @@ lreplace:
 static void
 ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 				   ResultRelInfo *resultRelInfo, ItemPointer tupleid,
-				   HeapTuple oldtuple, TupleTableSlot *slot)
+				   HeapTuple oldtuple, TupleTableSlot *slot, Oid recheckConstraints)
 {
 	ModifyTableState *mtstate = context->mtstate;
 	List	   *recheckIndexes = NIL;
@@ -2138,6 +2140,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 						 NULL, NULL,
 						 tupleid, oldtuple, slot,
 						 recheckIndexes,
+						 recheckConstraints,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
 						 mtstate->mt_transition_capture,
@@ -2225,7 +2228,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
 	/* Perform the root table's triggers. */
 	ExecARUpdateTriggers(context->estate,
 						 rootRelInfo, sourcePartInfo, destPartInfo,
-						 tupleid, NULL, newslot, NIL, NULL, true);
+						 tupleid, NULL, newslot, NIL, InvalidOid, NULL, true);
 }
 
 /* ----------------------------------------------------------------
@@ -2264,6 +2267,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
+	Oid			recheckConstraints = false;
 
 	/*
 	 * abort the operation if not running transactions
@@ -2320,7 +2324,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 		 */
 redo_act:
 		result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
-							   canSetTag, &updateCxt);
+							   canSetTag, &updateCxt, &recheckConstraints);
 
 		/*
 		 * If ExecUpdateAct reports that a cross-partition update was done,
@@ -2476,7 +2480,7 @@ redo_act:
 		(estate->es_processed)++;
 
 	ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
-					   slot);
+					   slot, recheckConstraints);
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -2845,6 +2849,7 @@ lmerge_matched:
 		CmdType		commandType = relaction->mas_action->commandType;
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
+		Oid			recheckConstraints = false;
 
 		/*
 		 * Test condition, if any.
@@ -2898,11 +2903,11 @@ lmerge_matched:
 					break;		/* concurrent update/delete */
 				}
 				result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
-									   newslot, false, &updateCxt);
+									   newslot, false, &updateCxt, &recheckConstraints);
 				if (result == TM_Ok && updateCxt.updated)
 				{
 					ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
-									   tupleid, NULL, newslot);
+									   tupleid, NULL, newslot, recheckConstraints);
 					mtstate->mt_merge_updated += 1;
 				}
 				break;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 243c8fb1e4..e45447a29a 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1244,15 +1244,16 @@ get_relation_constraints(PlannerInfo *root,
 			Node	   *cexpr;
 
 			/*
-			 * If this constraint hasn't been fully validated yet, we must
-			 * ignore it here.  Also ignore if NO INHERIT and we weren't told
-			 * that that's safe.
+			 * Ignore if this is deferred CHECK constraint or constraint
+			 * hasn't been fully validated yet.  Also ignore if NO INHERIT and
+			 * we weren't told that that's safe.
 			 */
+			if (constr->check[i].ccdeferrable)
+				continue;
 			if (!constr->check[i].ccvalid)
 				continue;
 			if (constr->check[i].ccnoinherit && !include_noinherit)
 				continue;
-
 			cexpr = stringToNode(constr->check[i].ccbin);
 
 			/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d2032885e..7e217c74e1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4077,7 +4077,7 @@ ConstraintElem:
 					n->raw_expr = $3;
 					n->cooked_expr = NULL;
 					processCASbits($5, @5, "CHECK",
-								   NULL, NULL, &n->skip_validation,
+								   &n->deferrable, &n->initdeferred, &n->skip_validation,
 								   &n->is_no_inherit, yyscanner);
 					n->initially_valid = !n->skip_validation;
 					$$ = (Node *) n;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index cf0d432ab1..5f2e67ea93 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -335,6 +335,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	 *
 	 * For regular tables all constraints can be marked valid immediately,
 	 * because the table is new therefore empty. Not so for foreign tables.
+	 * Also, Create After Row trigger(for Insert and Update) for Deferrable
+	 * check constraint.
 	 */
 	transformCheckConstraints(&cxt, !cxt.isforeign);
 
@@ -1407,6 +1409,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 		{
 			char	   *ccname = constr->check[ccnum].ccname;
 			char	   *ccbin = constr->check[ccnum].ccbin;
+			bool		ccdeferrable = constr->check[ccnum].ccdeferrable;
+			bool		ccdeferred = constr->check[ccnum].ccdeferred;
 			bool		ccnoinherit = constr->check[ccnum].ccnoinherit;
 			Node	   *ccbin_node;
 			bool		found_whole_row;
@@ -1436,6 +1440,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 			n->contype = CONSTR_CHECK;
 			n->conname = pstrdup(ccname);
 			n->location = -1;
+			n->deferrable = ccdeferrable;
+			n->initdeferred = ccdeferred;
 			n->is_no_inherit = ccnoinherit;
 			n->raw_expr = NULL;
 			n->cooked_expr = nodeToString(ccbin_node);
@@ -3771,7 +3777,8 @@ transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList)
 	 ((node)->contype == CONSTR_PRIMARY ||	\
 	  (node)->contype == CONSTR_UNIQUE ||	\
 	  (node)->contype == CONSTR_EXCLUSION || \
-	  (node)->contype == CONSTR_FOREIGN))
+	  (node)->contype == CONSTR_FOREIGN || \
+	  (node)->contype == CONSTR_CHECK))
 
 	foreach(clist, constraintList)
 	{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7234cb3da6..136e1e1d8a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4559,6 +4559,8 @@ CheckConstraintFetch(Relation relation)
 			break;
 		}
 
+		check[found].ccdeferrable = conform->condeferrable;
+		check[found].ccdeferred = conform->condeferred;
 		check[found].ccvalid = conform->convalidated;
 		check[found].ccnoinherit = conform->connoinherit;
 		check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index ffd2874ee3..17af20505d 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -29,6 +29,8 @@ typedef struct ConstrCheck
 {
 	char	   *ccname;
 	char	   *ccbin;			/* nodeToString representation of expr */
+	bool		ccdeferrable;
+	bool		ccdeferred;
 	bool		ccvalid;
 	bool		ccnoinherit;	/* this is a non-inheritable constraint */
 } ConstrCheck;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 51f7b12aa3..c7560e0c78 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -40,6 +40,8 @@ typedef struct CookedConstraint
 	char	   *name;			/* name, or NULL if none */
 	AttrNumber	attnum;			/* which attr (only for NOTNULL, DEFAULT) */
 	Node	   *expr;			/* transformed default or check expr */
+	bool		is_deferrable;	/* is deferrable (only for CHECK) */
+	bool		is_deferred;	/* is deferred (only for CHECK) */
 	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 */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..60b768f8dd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3898,6 +3898,11 @@
   proname => 'unique_key_recheck', provolatile => 'v', prorettype => 'trigger',
   proargtypes => '', prosrc => 'unique_key_recheck' },
 
+# Deferrable unique constraint trigger
+{ oid => '1382', descr => 'deferred CHECK constraint check',
+  proname => 'check_constraint_recheck', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'check_constraint_recheck' },
+
 # Generic referential integrity constraint triggers
 { oid => '1644', descr => 'referential integrity FOREIGN KEY ... REFERENCES',
   proname => 'RI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 430e3ca7dd..6798490b61 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -197,6 +197,7 @@ extern void ExecARInsertTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
 								 TupleTableSlot *slot,
 								 List *recheckIndexes,
+								 Oid recheckConstraints,
 								 TransitionCaptureState *transition_capture);
 extern bool ExecIRInsertTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
@@ -244,6 +245,7 @@ extern void ExecARUpdateTriggers(EState *estate,
 								 HeapTuple fdw_trigtuple,
 								 TupleTableSlot *newslot,
 								 List *recheckIndexes,
+								 Oid recheckConstraints,
 								 TransitionCaptureState *transition_capture,
 								 bool is_crosspart_update);
 extern bool ExecIRUpdateTriggers(EState *estate,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index aeebe0e0ff..2d7ec2f245 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -194,6 +194,44 @@ ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
 }
 #endif
 
+/*
+ * Enumeration specifying the type for re-check of CHECK constraint to perform in
+ * ExecConstraints().
+ *
+ * CHECK_DEFERRED_NO is the traditional Postgres immediate check, should
+ * throw an error if there is any check constraint violation. This is useful
+ * for command like CopyFrom.
+ *
+ * For deferrable CHECK constraints, CHECK_DEFERRED_YES is passed to
+ * to ExecConstraints for insert or update queries. ExecConstraints() should
+ * validate if the CHECK constraint is violated but should not throw an error,
+ * block, or prevent the insertion. We'll recheck later when it is time for the
+ * constraint to be enforced.  The  ExecConstraints() must return false if the tuple is
+ * not violating any check constraint, true if it is possibly a violation and we need
+ * to recheck the CHECK constraint.  In the "false" case
+ * it is safe to omit the later recheck.
+ *
+ * When it is time to recheck the deferred constraint(via AR trigger), a
+ * call is made with CHECK_DEFERRED_EXISTING and this time conflicting latest live tuple
+ * will be revalidated.
+ */
+typedef enum EnforceDeferredCheck
+{
+	CHECK_DEFERRED_NO,			/* Recheck of CHECK constraint is disabled, so
+								 * DEFERRED CHECK constraint will be
+								 * considered as non-deferrable check
+								 * constraint.  */
+	CHECK_DEFERRED_YES,			/* Recheck of CHECK constraint is enabled, so
+								 * CHECK constraint will be validated but
+								 * error will not be reported for deferred
+								 * CHECK constraint. */
+	CHECK_DEFERRED_EXISTING		/* Recheck of existing violated CHECK
+								 * constraint, indicates that this is a
+								 * deferred recheck of a row that was reported
+								 * as a potential violation of CHECK
+								 * CONSTRAINT */
+} EnforceDeferredCheck;
+
 /*
  * prototypes from functions in execMain.c
  */
@@ -219,8 +257,8 @@ extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid,
 											  ResultRelInfo *rootRelInfo);
 extern List *ExecGetAncestorResultRels(EState *estate, ResultRelInfo *resultRelInfo);
-extern void ExecConstraints(ResultRelInfo *resultRelInfo,
-							TupleTableSlot *slot, EState *estate);
+extern Oid	ExecConstraints(ResultRelInfo *resultRelInfo,
+							TupleTableSlot *slot, EState *estate, EnforceDeferredCheck checkConstraint);
 extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
 							   TupleTableSlot *slot, EState *estate, bool emitError);
 extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out
index 5b068477bf..50c58a615a 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -636,6 +636,146 @@ COMMIT;
 ERROR:  duplicate key value violates unique constraint "parted_uniq_tbl_1_i_key"
 DETAIL:  Key (i)=(1) already exists.
 DROP TABLE parted_uniq_tbl;
+-- deferrable CHECK constraint
+CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text); -- initially Immediate
+INSERT INTO check_constr_tbl VALUES (1, 'one');
+-- default is immediate so this should fail right away
+INSERT INTO check_constr_tbl VALUES (0, 'zero');
+ERROR:  new row for relation "check_constr_tbl" violates check constraint "check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0, zero).
+-- should fail here too
+BEGIN;
+INSERT INTO check_constr_tbl VALUES (0, 'zero');
+ERROR:  new row for relation "check_constr_tbl" violates check constraint "check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0, zero).
+COMMIT;
+-- explicitly defer the constraint
+BEGIN;
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'one');-- should succeed
+COMMIT; -- should fail
+ERROR:  new row for relation "check_constr_tbl" violates check constraint "check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0, one).
+BEGIN;
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should succeed
+UPDATE check_constr_tbl SET i = 1 WHERE t = 'one';
+COMMIT; -- should succeed
+-- INSERT Followed by UPDATE, UPDATE
+BEGIN;
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (3, 'three'); -- should succeed
+UPDATE check_constr_tbl SET i = 0 WHERE t = 'three' and i = 3; -- should succeed
+UPDATE check_constr_tbl SET i = 3 WHERE t = 'three' and i = 0; -- should succeed
+COMMIT; -- should succeed
+-- INSERT Followed by DELETE
+BEGIN;
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'zero'); -- should succeed
+DELETE FROM check_constr_tbl where i = 0; -- should succeed
+COMMIT; -- should succeed
+-- try adding an initially deferred constraint
+ALTER TABLE check_constr_tbl DROP CONSTRAINT check_constr_tbl_i_check;
+ALTER TABLE check_constr_tbl ADD CONSTRAINT check_constr_tbl_i_check
+	CHECK (i<>0) DEFERRABLE INITIALLY DEFERRED;
+BEGIN;
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should succeed
+COMMIT; -- should fail
+ERROR:  new row for relation "check_constr_tbl" violates check constraint "check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0, one).
+BEGIN;
+SET CONSTRAINTS ALL IMMEDIATE;
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should fail
+ERROR:  new row for relation "check_constr_tbl" violates check constraint "check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0, one).
+COMMIT;
+-- test deferrable CHECK constraint with a partition table
+CREATE TABLE parted_check_constr_tbl (i int check(i<>0) DEFERRABLE) partition by range (i);
+CREATE TABLE parted_check_constr_tbl_1 PARTITION OF parted_check_constr_tbl FOR VALUES FROM (0) TO (10);
+CREATE TABLE parted_check_constr_tbl_2 PARTITION OF parted_check_constr_tbl FOR VALUES FROM (20) TO (30);
+SELECT conname, conrelid::regclass FROM pg_constraint
+  WHERE conname LIKE 'parted_check%' ORDER BY conname;
+             conname             |         conrelid          
+---------------------------------+---------------------------
+ parted_check_constr_tbl_i_check | parted_check_constr_tbl
+ parted_check_constr_tbl_i_check | parted_check_constr_tbl_1
+ parted_check_constr_tbl_i_check | parted_check_constr_tbl_2
+(3 rows)
+
+BEGIN;
+INSERT INTO parted_check_constr_tbl VALUES (1);
+SAVEPOINT f;
+UPDATE parted_check_constr_tbl set i = 0 where i = 1; -- check constraint violation
+ERROR:  new row for relation "parted_check_constr_tbl_1" violates check constraint "parted_check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0).
+ROLLBACK TO f;
+SET CONSTRAINTS parted_check_constr_tbl_i_check DEFERRED;
+UPDATE parted_check_constr_tbl set i = 0 where i = 1; -- now succeed
+COMMIT; -- should fail
+ERROR:  new row for relation "parted_check_constr_tbl_1" violates check constraint "parted_check_constr_tbl_i_check"
+DETAIL:  Failing row contains (0).
+-- test table inheritance, must inhert column i DEFERRABLE check constraint
+CREATE TABLE parent_check_deferred ( i int CHECK(i<>0) DEFERRABLE INITIALLY DEFERRED);
+CREATE TABLE child_check_deferred ( j int) INHERITS (parent_check_deferred);
+\d+ child_check_deferred;
+                           Table "public.child_check_deferred"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i      | integer |           |          |         | plain   |              | 
+ j      | integer |           |          |         | plain   |              | 
+Check constraints:
+    "parent_check_deferred_i_check" CHECK (i <> 0) DEFERRABLE INITIALLY DEFERRED
+Inherits: parent_check_deferred
+
+CREATE TABLE check_deferred_1 (a int, b int);
+ALTER TABLE check_deferred_1 ADD CONSTRAINT a_check CHECK (a > 0);
+ALTER TABLE check_deferred_1 ADD CONSTRAINT b_check CHECK (b > 0) DEFERRABLE;
+-- test constraint exclusion logic
+CREATE TABLE check_deferred_ex (a int);
+CREATE TABLE check_deferred_ex_c1 () INHERITS (check_deferred_ex);
+CREATE TABLE check_deferred_ex_c2 () INHERITS (check_deferred_ex);
+ALTER TABLE check_deferred_ex_c2 ADD CONSTRAINT cc CHECK (a != 5) INITIALLY DEFERRED;
+BEGIN;
+INSERT INTO check_deferred_ex_c2 VALUES (5);
+SET LOCAL constraint_exclusion TO off;
+SELECT * FROM check_deferred_ex WHERE a = 5;
+ a 
+---
+ 5
+(1 row)
+
+SET LOCAL constraint_exclusion TO on;
+SELECT * FROM check_deferred_ex WHERE a = 5;
+ a 
+---
+ 5
+(1 row)
+
+COMMIT; --should fail
+ERROR:  new row for relation "check_deferred_ex_c2" violates check constraint "cc"
+DETAIL:  Failing row contains (5).
+-- test merge constraint logic
+CREATE TABLE p (a int, b int);
+ALTER TABLE p ADD CONSTRAINT c1 CHECK (a > 0) DEFERRABLE;
+ALTER TABLE p ADD CONSTRAINT c2 CHECK (b > 0);
+CREATE TABLE q () INHERITS (p);
+ALTER TABLE q ADD CONSTRAINT c1 CHECK (a > 0); --should fail
+ERROR:  constraint "c1" for relation "q" already exists
+ALTER TABLE q ADD CONSTRAINT c2 CHECK (b > 0) DEFERRABLE; --should fail
+ERROR:  constraint "c2" for relation "q" already exists
+-- clean up
+DROP TABLE child_check_deferred;
+DROP TABLE parent_check_deferred;
+DROP TABLE parted_check_constr_tbl_1;
+DROP TABLE parted_check_constr_tbl_2;
+DROP TABLE parted_check_constr_tbl;
+DROP TABLE check_constr_tbl;
+DROP TABLE check_deferred_1;
+DROP TABLE check_deferred_ex_c2;
+DROP TABLE check_deferred_ex_c1;
+DROP TABLE check_deferred_ex;
+DROP TABLE q;
+DROP TABLE p;
 -- test naming a constraint in a partition when a conflict exists
 CREATE TABLE parted_fk_naming (
     id bigint NOT NULL default 1,
diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql
index a7d96e98f5..f06cc97548 100644
--- a/src/test/regress/sql/constraints.sql
+++ b/src/test/regress/sql/constraints.sql
@@ -446,6 +446,139 @@ INSERT INTO parted_uniq_tbl VALUES (1);	-- OK now, fail at commit
 COMMIT;
 DROP TABLE parted_uniq_tbl;
 
+
+-- deferrable CHECK constraint
+CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text); -- initially Immediate
+
+INSERT INTO check_constr_tbl VALUES (1, 'one');
+
+-- default is immediate so this should fail right away
+INSERT INTO check_constr_tbl VALUES (0, 'zero');
+
+-- should fail here too
+BEGIN;
+
+INSERT INTO check_constr_tbl VALUES (0, 'zero');
+
+COMMIT;
+
+-- explicitly defer the constraint
+BEGIN;
+
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'one');-- should succeed
+
+COMMIT; -- should fail
+
+BEGIN;
+
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should succeed
+UPDATE check_constr_tbl SET i = 1 WHERE t = 'one';
+
+COMMIT; -- should succeed
+
+-- INSERT Followed by UPDATE, UPDATE
+BEGIN;
+
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (3, 'three'); -- should succeed
+UPDATE check_constr_tbl SET i = 0 WHERE t = 'three' and i = 3; -- should succeed
+UPDATE check_constr_tbl SET i = 3 WHERE t = 'three' and i = 0; -- should succeed
+
+COMMIT; -- should succeed
+
+-- INSERT Followed by DELETE
+BEGIN;
+
+SET CONSTRAINTS check_constr_tbl_i_check DEFERRED;
+INSERT INTO check_constr_tbl VALUES (0, 'zero'); -- should succeed
+DELETE FROM check_constr_tbl where i = 0; -- should succeed
+
+COMMIT; -- should succeed
+
+-- try adding an initially deferred constraint
+ALTER TABLE check_constr_tbl DROP CONSTRAINT check_constr_tbl_i_check;
+ALTER TABLE check_constr_tbl ADD CONSTRAINT check_constr_tbl_i_check
+	CHECK (i<>0) DEFERRABLE INITIALLY DEFERRED;
+
+BEGIN;
+
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should succeed
+
+COMMIT; -- should fail
+
+BEGIN;
+
+SET CONSTRAINTS ALL IMMEDIATE;
+
+INSERT INTO check_constr_tbl VALUES (0, 'one'); -- should fail
+
+COMMIT;
+
+
+-- test deferrable CHECK constraint with a partition table
+CREATE TABLE parted_check_constr_tbl (i int check(i<>0) DEFERRABLE) partition by range (i);
+CREATE TABLE parted_check_constr_tbl_1 PARTITION OF parted_check_constr_tbl FOR VALUES FROM (0) TO (10);
+CREATE TABLE parted_check_constr_tbl_2 PARTITION OF parted_check_constr_tbl FOR VALUES FROM (20) TO (30);
+SELECT conname, conrelid::regclass FROM pg_constraint
+  WHERE conname LIKE 'parted_check%' ORDER BY conname;
+BEGIN;
+INSERT INTO parted_check_constr_tbl VALUES (1);
+SAVEPOINT f;
+UPDATE parted_check_constr_tbl set i = 0 where i = 1; -- check constraint violation
+ROLLBACK TO f;
+SET CONSTRAINTS parted_check_constr_tbl_i_check DEFERRED;
+UPDATE parted_check_constr_tbl set i = 0 where i = 1; -- now succeed
+COMMIT; -- should fail
+
+-- test table inheritance, must inhert column i DEFERRABLE check constraint
+CREATE TABLE parent_check_deferred ( i int CHECK(i<>0) DEFERRABLE INITIALLY DEFERRED);
+CREATE TABLE child_check_deferred ( j int) INHERITS (parent_check_deferred);
+\d+ child_check_deferred;
+
+CREATE TABLE check_deferred_1 (a int, b int);
+ALTER TABLE check_deferred_1 ADD CONSTRAINT a_check CHECK (a > 0);
+ALTER TABLE check_deferred_1 ADD CONSTRAINT b_check CHECK (b > 0) DEFERRABLE;
+
+-- test constraint exclusion logic
+CREATE TABLE check_deferred_ex (a int);
+CREATE TABLE check_deferred_ex_c1 () INHERITS (check_deferred_ex);
+CREATE TABLE check_deferred_ex_c2 () INHERITS (check_deferred_ex);
+ALTER TABLE check_deferred_ex_c2 ADD CONSTRAINT cc CHECK (a != 5) INITIALLY DEFERRED;
+BEGIN;
+INSERT INTO check_deferred_ex_c2 VALUES (5);
+SET LOCAL constraint_exclusion TO off;
+SELECT * FROM check_deferred_ex WHERE a = 5;
+SET LOCAL constraint_exclusion TO on;
+SELECT * FROM check_deferred_ex WHERE a = 5;
+COMMIT; --should fail
+
+-- test merge constraint logic
+CREATE TABLE p (a int, b int);
+ALTER TABLE p ADD CONSTRAINT c1 CHECK (a > 0) DEFERRABLE;
+ALTER TABLE p ADD CONSTRAINT c2 CHECK (b > 0);
+
+CREATE TABLE q () INHERITS (p);
+ALTER TABLE q ADD CONSTRAINT c1 CHECK (a > 0); --should fail
+ALTER TABLE q ADD CONSTRAINT c2 CHECK (b > 0) DEFERRABLE; --should fail
+
+-- clean up
+DROP TABLE child_check_deferred;
+DROP TABLE parent_check_deferred;
+DROP TABLE parted_check_constr_tbl_1;
+DROP TABLE parted_check_constr_tbl_2;
+DROP TABLE parted_check_constr_tbl;
+DROP TABLE check_constr_tbl;
+DROP TABLE check_deferred_1;
+DROP TABLE check_deferred_ex_c2;
+DROP TABLE check_deferred_ex_c1;
+DROP TABLE check_deferred_ex;
+DROP TABLE q;
+DROP TABLE p;
+
+
+
 -- test naming a constraint in a partition when a conflict exists
 CREATE TABLE parted_fk_naming (
     id bigint NOT NULL default 1,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8de90c4958..e66dc59c7f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -334,6 +334,7 @@ CCHashFN
 CEOUC_WAIT_MODE
 CFuncHashTabEntry
 CHAR
+EnforceDeferredCheck
 CHECKPOINT
 CHKVAL
 CIRCLE
-- 
2.25.1

