CHECK Constraint Deferrable

Started by Himanshu Upadhyayaover 2 years ago28 messages
#1Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
1 attachment(s)

Hi,

Currently, there is no support for CHECK constraint DEFERRABLE in a create
table statement.
SQL standard specifies that CHECK constraint can be defined as DEFERRABLE.

The attached patch is having implementation for CHECK constraint Deferrable
as below:

‘postgres[579453]=#’CREATE TABLE t1 (i int CHECK(i<>0) DEFERRABLE, t text);
CREATE TABLE
‘postgres[579453]=#’\d t1
Table "public.t1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
t | text | | |
Check constraints:
"t1_i_check" CHECK (i <> 0) DEFERRABLE

Now we can have a deferrable CHECK constraint, and we can defer the
constraint validation:

‘postgres[579453]=#’BEGIN;
BEGIN
‘postgres[579453]=#*’SET CONSTRAINTS t1_i_check DEFERRED;
SET CONSTRAINTS
‘postgres[579453]=#*’INSERT INTO t1 VALUES (0, 'one'); -- should succeed
INSERT 0 1
‘postgres[579453]=#*’UPDATE t1 SET i = 1 WHERE t = 'one';
UPDATE 1
‘postgres[579453]=#*’COMMIT; -- should succeed
COMMIT

Attaching the initial patch, I will improve it with documentation in my
next version of the patch.

thoughts?

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

Attachments:

v1-0001-Implementation-of-CHECK-Constraint-to-make-it-Def.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Implementation-of-CHECK-Constraint-to-make-it-Def.patchDownload
From 1eb72b14a3a6914e893854508a071ae835d23245 Mon Sep 17 00:00:00 2001
From: Himanshu Upadhyaya <himanshu.upadhyaya@enterprisedb.com>
Date: Wed, 5 Jul 2023 14:54:37 +0530
Subject: [PATCH v1] Implementation of "CHECK Constraint" to make it
 Deferrable.

---
 src/backend/access/common/tupdesc.c       |   2 +
 src/backend/catalog/heap.c                |  50 ++++++++--
 src/backend/commands/constraint.c         | 116 ++++++++++++++++++++++
 src/backend/commands/copyfrom.c           |  10 +-
 src/backend/commands/tablecmds.c          |   8 ++
 src/backend/commands/trigger.c            |  43 ++++++--
 src/backend/executor/execMain.c           |  33 +++++-
 src/backend/executor/execReplication.c    |  10 +-
 src/backend/executor/nodeModifyTable.c    |  29 +++---
 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 |  98 ++++++++++++++++++
 src/test/regress/sql/constraints.sql      |  99 ++++++++++++++++++
 19 files changed, 518 insertions(+), 46 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 7c5c390503..098cb27932 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;
 			}
 		}
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2a0d82aedd..8a1e8e266f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -52,17 +52,20 @@
 #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 "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #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"
@@ -102,7 +105,8 @@ static ObjectAddress AddNewRelationType(const char *typeName,
 static void RelationRemoveInheritance(Oid relid);
 static Oid	StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 						  bool is_validated, bool is_local, int inhcount,
-						  bool is_no_inherit, bool is_internal);
+						  bool is_no_inherit, bool is_internal,
+						  bool is_deferrable, bool initdeferred);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
 							 bool is_internal);
 static bool MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
@@ -2063,13 +2067,15 @@ SetAttrMissing(Oid relid, char *attname, char *value)
 static Oid
 StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
-			  bool is_no_inherit, bool is_internal)
+			  bool is_no_inherit, bool is_internal,
+			  bool is_deferrable, bool initdeferred)
 {
 	char	   *ccbin;
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
 	Oid			constrOid;
+	CreateTrigStmt *trigger;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -2126,8 +2132,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 */
@@ -2155,6 +2163,36 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_no_inherit,	/* connoinherit */
 							  is_internal); /* internally constructed? */
 
+	/*
+	 * 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)
+	{
+		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);
 
 	return constrOid;
@@ -2201,7 +2239,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 					StoreRelCheck(rel, con->name, con->expr,
 								  !con->skip_validation, con->is_local,
 								  con->inhcount, con->is_no_inherit,
-								  is_internal);
+								  is_internal, con->is_deferrable, con->is_deferred);
 				numchecks++;
 				break;
 			default:
@@ -2453,7 +2491,7 @@ AddRelationNewConstraints(Relation rel,
 		 */
 		constrOid =
 			StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
-						  is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
+						  is_local ? 0 : 1, cdef->is_no_inherit, is_internal, cdef->deferrable, cdef->initdeferred);
 
 		numchecks++;
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 35c4451fc0..0dc474d447 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's 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_RECHECK_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 80bca79cd0..975b5fdc4d 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, false,
 										 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, false,
 									 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, false,
 									 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_RECHECK_DISABLED);
 
 				/*
 				 * 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, false, cstate->transition_capture);
 
 					list_free(recheckIndexes);
 				}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fce5e6f220..099b2ee1f6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -924,6 +924,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cooked->is_local = true;	/* not used for defaults */
 			cooked->inhcount = 0;	/* ditto */
 			cooked->is_no_inherit = false;
+			cooked->is_deferrable = false;	/* By default constraint is not
+											 * deferrable */
+			cooked->is_deferred = false;	/* ditto */
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
@@ -2841,6 +2844,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					cooked->is_local = false;
 					cooked->inhcount = 1;
 					cooked->is_no_inherit = false;
+					cooked->is_deferrable = check[i].ccdeferrable;
+					cooked->is_deferred = check[i].ccdeferred;
 					constraints = lappend(constraints, cooked);
 				}
 			}
@@ -18810,6 +18815,9 @@ DetachAddConstraintIfNeeded(List **wqueue, Relation partRel)
 		n->cooked_expr = nodeToString(make_ands_explicit(constraintExpr));
 		n->initially_valid = true;
 		n->skip_validation = true;
+		n->deferrable = false;	/* By default this new constraint must be
+								 * non-deferrable */
+		n->initdeferred = false;	/* Ditto */
 		/* It's a re-add, since it nominally already exists */
 		ATAddCheckConstraint(wqueue, tab, partRel, n,
 							 true, false, true, ShareUpdateExclusiveLock);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 52177759ab..9f582fcfd7 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, bool 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, false, 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)
+					 bool 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, false, 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, false, 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, false,
 							  ExecGetAllUpdatedCols(relinfo, estate),
 							  transition_capture,
 							  false);
@@ -3089,6 +3092,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple fdw_trigtuple,
 					 TupleTableSlot *newslot,
 					 List *recheckIndexes,
+					 bool 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, false, 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, bool recheckConstraints,
+					  Bitmapset *modifiedCols,
 					  TransitionCaptureState *transition_capture,
 					  bool is_crosspart_update)
 {
@@ -6064,6 +6069,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 	int			tgtype_level;
 	int			i;
 	Tuplestorestate *fdw_tuplestore = NULL;
+	bool		isChkConRechkQueued = false;
 
 	/*
 	 * Check state.  We use a normal test not Assert because it is possible to
@@ -6389,6 +6395,23 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			}
 		}
 
+		/*
+		 * If the trigger is deferred check constraint recheck trigger, only
+		 * queue it if any of the check constraint was potentially violated
+		 * and no check constraint trigger is yet added(queued).
+		 */
+		if (trigger->tgfoid == F_CHECK_CONSTRAINT_RECHECK)
+		{
+			if (!recheckConstraints || isChkConRechkQueued)
+			{
+				continue;
+			}
+			else
+			{
+				isChkConRechkQueued = true;
+			}
+		}
+
 		/*
 		 * 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..6d6dd713c1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1734,12 +1734,14 @@ ExecutePlan(EState *estate,
 
 /*
  * ExecRelCheck --- check that tuple meets constraints for result relation
+ * and reset recheckConstraints to true in case of any constraint violation and
+ * 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, checkConstraintRecheck checkConstraint, bool *recheckConstraints)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	int			ncheck = rel->rd_att->constr->num_check;
@@ -1798,7 +1800,20 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 		 * ExecQual.
 		 */
 		if (!ExecCheck(checkconstr, econtext))
+		{
+
+			/*
+			 * If the constraint is deferrable and caller is
+			 * CHECK_RECHECK_ENABLED 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_RECHECK_ENABLED && check[i].ccdeferrable)
+			{
+				*recheckConstraints = true;
+			}
 			return check[i].ccname;
+		}
 	}
 
 	/* NULL result means no error */
@@ -1936,18 +1951,25 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
  * have been converted from the original input tuple after tuple routing.
  * 'resultRelInfo' is the final result relation, after tuple routing.
  */
-void
+bool
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate)
+				TupleTableSlot *slot, EState *estate, checkConstraintRecheck checkConstraint)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
 	TupleConstr *constr = tupdesc->constr;
 	Bitmapset  *modifiedCols;
+	bool		recheckConstraints = false;
 
 	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_RECHECK_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_RECHECK_EXISTING)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -2015,7 +2037,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	{
 		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 +2082,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(orig_rel, failed)));
 		}
 	}
+	return recheckConstraints;
 }
 
 /*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 9dd7168461..c29b6a5200 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -477,6 +477,7 @@ ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
 	if (!skip_tuple)
 	{
 		List	   *recheckIndexes = NIL;
+		bool		recheckConstraints = false;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -486,7 +487,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_RECHECK_ENABLED);
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
@@ -500,7 +501,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
@@ -545,6 +546,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 	{
 		List	   *recheckIndexes = NIL;
 		TU_UpdateIndexes update_indexes;
+		bool		recheckConstraints = false;
 
 		/* Compute stored generated columns */
 		if (rel->rd_att->constr &&
@@ -554,7 +556,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_RECHECK_ENABLED);
 		if (rel->rd_rel->relispartition)
 			ExecPartitionCheck(resultRelInfo, slot, estate, true);
 
@@ -571,7 +573,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 2a5fec8d01..022221f92c 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;
+	bool		recheckConstraints;
 
 	/*
 	 * 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_RECHECK_ENABLED);
 
 		/*
 		 * Also check the tuple against the partition constraint, if there is
@@ -1162,6 +1163,7 @@ ExecInsert(ModifyTableContext *context,
 							 NULL,
 							 slot,
 							 NULL,
+							 false,
 							 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, false,
 							 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, false, 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, bool *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_RECHECK_ENABLED);
 
 	/*
 	 * 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, bool 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, false, NULL, true);
 }
 
 /* ----------------------------------------------------------------
@@ -2264,6 +2267,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
+	bool		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};
+		bool		recheckConstraints = false;
 
 		/*
 		 * Test condition, if any.
@@ -2897,11 +2902,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/parser/gram.y b/src/backend/parser/gram.y
index 39ab7eac0d..f03c35bc0f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4076,7 +4076,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 d67580fc77..f78bb63520 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -333,6 +333,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);
 
@@ -1319,6 +1321,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 			char	   *ccname = constr->check[ccnum].ccname;
 			char	   *ccbin = constr->check[ccnum].ccbin;
 			bool		ccnoinherit = constr->check[ccnum].ccnoinherit;
+			bool		ccdeferrable = constr->check[ccnum].ccdeferrable;
+			bool		ccdeferred = constr->check[ccnum].ccdeferred;
 			Node	   *ccbin_node;
 			bool		found_whole_row;
 			Constraint *n;
@@ -1348,6 +1352,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 			n->conname = pstrdup(ccname);
 			n->location = -1;
 			n->is_no_inherit = ccnoinherit;
+			n->deferrable = ccdeferrable;
+			n->initdeferred = ccdeferred;
 			n->raw_expr = NULL;
 			n->cooked_expr = nodeToString(ccbin_node);
 
@@ -3649,7 +3655,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 8a08463c2b..cc997adeb4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4560,6 +4560,8 @@ CheckConstraintFetch(Relation relation)
 
 		check[found].ccvalid = conform->convalidated;
 		check[found].ccnoinherit = conform->connoinherit;
+		check[found].ccdeferrable = conform->condeferrable;
+		check[found].ccdeferred = conform->condeferred;
 		check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
 												  NameStr(conform->conname));
 
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index b4286cf922..87c18f6299 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -31,6 +31,8 @@ typedef struct ConstrCheck
 	char	   *ccbin;			/* nodeToString representation of expr */
 	bool		ccvalid;
 	bool		ccnoinherit;	/* this is a non-inheritable constraint */
+	bool		ccdeferrable;
+	bool		ccdeferred;
 } ConstrCheck;
 
 /* This structure contains constraints of a tuple */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d01ab504b6..1bfc3fe99a 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -44,6 +44,8 @@ typedef struct CookedConstraint
 	int			inhcount;		/* number of times constraint is inherited */
 	bool		is_no_inherit;	/* constraint has local def and cannot be
 								 * inherited */
+	bool		is_deferrable;	/* is deferrable (only for CHECK) */
+	bool		is_deferred;	/* is deferred (only for CHECK) */
 } CookedConstraint;
 
 extern Relation heap_create(const char *relname,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..8f893eb864 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3886,6 +3886,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..d13908939e 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,
+								 bool 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,
+								 bool 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 ac02247947..b30376399b 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_RECHECK_DISABLED 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_RECHECK_ENABLED 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_RECHECK_EXISTING and this time conflicting latest live tuple
+ * will be revalidate.
+ */
+typedef enum checkConstraintRecheck
+{
+	CHECK_RECHECK_DISABLED,		/* Recheck of CHECK constraint is disabled, so
+								 * DEFERRED CHECK constraint will be
+								 * considered as non-deferrable check
+								 * constraint.  */
+	CHECK_RECHECK_ENABLED,		/* Recheck of CHECK constraint is enabled, so
+								 * CHECK constraint will be validated but
+								 * error will not be reported for deferred
+								 * CHECK constraint. */
+	CHECK_RECHECK_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 */
+}			checkConstraintRecheck;
+
 /*
  * 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 bool ExecConstraints(ResultRelInfo *resultRelInfo,
+							TupleTableSlot *slot, EState *estate, checkConstraintRecheck 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 e6f6602d95..9d34067b6e 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -603,6 +603,104 @@ 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
+
+-- 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;
 -- 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 5ffcd4ffc7..5952ad53f6 100644
--- a/src/test/regress/sql/constraints.sql
+++ b/src/test/regress/sql/constraints.sql
@@ -430,6 +430,105 @@ 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;
+
+-- 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;
+
 -- test naming a constraint in a partition when a conflict exists
 CREATE TABLE parted_fk_naming (
     id bigint NOT NULL default 1,
-- 
2.25.1

#2Dilip Kumar
dilipbalaut@gmail.com
In reply to: Himanshu Upadhyaya (#1)
Re: CHECK Constraint Deferrable

On Wed, Jul 5, 2023 at 3:08 PM Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Hi,

Currently, there is no support for CHECK constraint DEFERRABLE in a create table statement.
SQL standard specifies that CHECK constraint can be defined as DEFERRABLE.

I think this is a valid argument that this is part of SQL standard so
it would be good addition to PostgreSQL. So +1 for the feature.

But I am wondering whether there are some real-world use cases for
deferred CHECK/NOT NULL constraints? I mean like for foreign key
constraints if there is a cyclic dependency between two tables then
deferring the constraint is the simplest way to insert without error.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

#3Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: Dilip Kumar (#2)
Re: CHECK Constraint Deferrable

I can think of one scenario, as below

1) any department should have an employee
2)any employee should be assigned to a department
so, the employee table has a FK to the department table, and another check
constraint should be added to the department table to ensure there should
be one/more employees in this department. It's kind of a deadlock
situation, each one depends on the other one. We cant insert a new
department, coz there is no employee. Also, we can't insert new employee
belongs to this new department, coz the department hasn't been and cant be
added. So if we have a check constraint defined as deferrable we can solve
this problem.

‘postgres[685143]=#’CREATE FUNCTION checkEmpPresent(did int) RETURNS int AS
$$ SELECT count(*) from emp where emp.deptno = did $$ IMMUTABLE LANGUAGE
SQL;
CREATE FUNCTION
‘postgres[685143]=#’alter table dept add constraint check_cons check
(checkEmpPresent(deptno) > 0);
ALTER TABLE
‘postgres[685143]=#’\d dept;
Table "public.dept"
Column | Type | Collation | Nullable | Default
----------+---------------+-----------+----------+---------
deptno | integer | | not null |
deptname | character(20) | | |
Indexes:
"dept_pkey" PRIMARY KEY, btree (deptno)
Check constraints:
"check_cons" CHECK (checkemppresent(deptno) > 0)
Referenced by:
TABLE "emp" CONSTRAINT "fk_cons" FOREIGN KEY (deptno) REFERENCES
dept(deptno)

‘postgres[685143]=#’insert into dept values (1, 'finance');
ERROR: 23514: new row for relation "dept" violates check constraint
"check_cons"
DETAIL: Failing row contains (1, finance ).
SCHEMA NAME: public
TABLE NAME: dept
CONSTRAINT NAME: check_cons
LOCATION: ExecConstraints, execMain.c:2069
‘postgres[685143]=#’\d emp;
Table "public.emp"
Column | Type | Collation | Nullable | Default
--------+---------------+-----------+----------+---------
empno | integer | | |
ename | character(20) | | |
deptno | integer | | |
Foreign-key constraints:
"fk_cons" FOREIGN KEY (deptno) REFERENCES dept(deptno)

‘postgres[685143]=#’insert into emp values (1001, 'test', 1);
ERROR: 23503: insert or update on table "emp" violates foreign key
constraint "fk_cons"
DETAIL: Key (deptno)=(1) is not present in table "dept".
SCHEMA NAME: public
TABLE NAME: emp
CONSTRAINT NAME: fk_cons
LOCATION: ri_ReportViolation, ri_triggers.c:2608

I have tried with v1 patch as below;

‘postgres[685143]=#’alter table dept drop constraint check_cons;
ALTER TABLE
‘postgres[685143]=#’alter table dept add constraint check_cons check
(checkEmpPresent(deptno) > 0) DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE
‘postgres[685143]=#’BEGIN;
BEGIN
‘postgres[685143]=#*’insert into dept values (1, 'finance');
INSERT 0 1
‘postgres[685143]=#*’insert into emp values (1001, 'test', 1);
INSERT 0 1
‘postgres[685143]=#*’commit;
COMMIT
‘postgres[685143]=#’select * from dept;
deptno | deptname
--------+----------------------
1 | finance
(1 row)

‘postgres[685143]=#’select * from emp;
empno | ename | deptno
-------+----------------------+--------
1001 | test | 1
(1 row)

Thanks,
Himanshu

On Fri, Jul 7, 2023 at 5:21 PM Dilip Kumar <dilipbalaut@gmail.com> wrote:

On Wed, Jul 5, 2023 at 3:08 PM Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Hi,

Currently, there is no support for CHECK constraint DEFERRABLE in a

create table statement.

SQL standard specifies that CHECK constraint can be defined as

DEFERRABLE.

I think this is a valid argument that this is part of SQL standard so
it would be good addition to PostgreSQL. So +1 for the feature.

But I am wondering whether there are some real-world use cases for
deferred CHECK/NOT NULL constraints? I mean like for foreign key
constraints if there is a cyclic dependency between two tables then
deferring the constraint is the simplest way to insert without error.

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#4David G. Johnston
david.g.johnston@gmail.com
In reply to: Himanshu Upadhyaya (#3)
Re: CHECK Constraint Deferrable

On Friday, July 7, 2023, Himanshu Upadhyaya <upadhyaya.himanshu@gmail.com>
wrote:

I can think of one scenario, as below

1) any department should have an employee
2)any employee should be assigned to a department
so, the employee table has a FK to the department table, and another check
constraint should be added to the department table to ensure there should
be one/more employees in this department.

That isn’t a valid/allowed check constraint - it contains a prohibited
reference to another table.

David J.

#5Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: David G. Johnston (#4)
1 attachment(s)
Re: CHECK Constraint Deferrable

Attached is v2 of the patch, rebased against the latest HEAD.

Thanks,
Himanshu

Attachments:

v2-0001-Implementation-of-CHECK-Constraint-to-make-it.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Implementation-of-CHECK-Constraint-to-make-it.patchDownload
From cf6057ebeffd026ae075ec43d573eca1164eff5b Mon Sep 17 00:00:00 2001
From: Himanshu Upadhyaya <himanshu.upadhyaya@enterprisedb.com>
Date: Thu, 7 Sep 2023 13:19:14 +0530
Subject: [PATCH v2] Implementation of "CHECK Constraint" to make it

Deferrable.
---
 src/backend/access/common/tupdesc.c       |   2 +
 src/backend/catalog/heap.c                |  50 ++++++++--
 src/backend/commands/constraint.c         | 116 ++++++++++++++++++++++
 src/backend/commands/copyfrom.c           |  10 +-
 src/backend/commands/tablecmds.c          |   8 ++
 src/backend/commands/trigger.c            |  43 ++++++--
 src/backend/executor/execMain.c           |  33 +++++-
 src/backend/executor/execReplication.c    |  10 +-
 src/backend/executor/nodeModifyTable.c    |  29 +++---
 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 |  98 ++++++++++++++++++
 src/test/regress/sql/constraints.sql      |  99 ++++++++++++++++++
 19 files changed, 518 insertions(+), 46 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 7c5c390503..098cb27932 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;
 			}
 		}
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b42711f574..b595a60b42 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -52,17 +52,20 @@
 #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 "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_coerce.h"
 #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"
@@ -102,7 +105,8 @@ static ObjectAddress AddNewRelationType(const char *typeName,
 static void RelationRemoveInheritance(Oid relid);
 static Oid	StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 						  bool is_validated, bool is_local, int inhcount,
-						  bool is_no_inherit, bool is_internal);
+						  bool is_no_inherit, bool is_internal,
+						  bool is_deferrable, bool initdeferred);
 static void StoreConstraints(Relation rel, List *cooked_constraints,
 							 bool is_internal);
 static bool MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
@@ -2050,13 +2054,15 @@ SetAttrMissing(Oid relid, char *attname, char *value)
 static Oid
 StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 			  bool is_validated, bool is_local, int inhcount,
-			  bool is_no_inherit, bool is_internal)
+			  bool is_no_inherit, bool is_internal,
+			  bool is_deferrable, bool initdeferred)
 {
 	char	   *ccbin;
 	List	   *varList;
 	int			keycount;
 	int16	   *attNos;
 	Oid			constrOid;
+	CreateTrigStmt *trigger;
 
 	/*
 	 * Flatten expression to string form for storage.
@@ -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 */
@@ -2142,6 +2150,36 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_no_inherit,	/* connoinherit */
 							  is_internal); /* internally constructed? */
 
+	/*
+	 * 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)
+	{
+		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);
 
 	return constrOid;
@@ -2235,7 +2273,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 					StoreRelCheck(rel, con->name, con->expr,
 								  !con->skip_validation, con->is_local,
 								  con->inhcount, con->is_no_inherit,
-								  is_internal);
+								  is_internal, con->is_deferrable, con->is_deferred);
 				numchecks++;
 				break;
 
@@ -2500,7 +2538,7 @@ AddRelationNewConstraints(Relation rel,
 			 */
 			constrOid =
 				StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
-							  is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
+							  is_local ? 0 : 1, cdef->is_no_inherit, is_internal, cdef->deferrable, cdef->initdeferred);
 
 			numchecks++;
 
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index 35c4451fc0..6f67b9424f 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_RECHECK_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 b47cb5c66d..7df47017c9 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, false,
 										 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, false,
 									 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, false,
 									 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_RECHECK_DISABLED);
 
 				/*
 				 * 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, false, cstate->transition_capture);
 
 					list_free(recheckIndexes);
 				}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 47c556669f..36da96c8f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -938,6 +938,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cooked->is_local = true;	/* not used for defaults */
 			cooked->inhcount = 0;	/* ditto */
 			cooked->is_no_inherit = false;
+			cooked->is_deferrable = false;	/* By default constraint is not
+											 * deferrable */
+			cooked->is_deferred = false;	/* ditto */
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
@@ -2930,6 +2933,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					cooked->is_local = false;
 					cooked->inhcount = 1;
 					cooked->is_no_inherit = false;
+					cooked->is_deferrable = check[i].ccdeferrable;
+					cooked->is_deferred = check[i].ccdeferred;
 					constraints = lappend(constraints, cooked);
 				}
 			}
@@ -19543,6 +19548,9 @@ DetachAddConstraintIfNeeded(List **wqueue, Relation partRel)
 		n->cooked_expr = nodeToString(make_ands_explicit(constraintExpr));
 		n->initially_valid = true;
 		n->skip_validation = true;
+		n->deferrable = false;  /* By default this new constraint must be
+								 * non-deferrable */
+		n->initdeferred = false;    /* Ditto */
 		/* It's a re-add, since it nominally already exists */
 		ATAddCheckNNConstraint(wqueue, tab, partRel, n,
 							   true, false, true, ShareUpdateExclusiveLock);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 52177759ab..9f582fcfd7 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, bool 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, false, 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)
+					 bool 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, false, 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, false, 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, false,
 							  ExecGetAllUpdatedCols(relinfo, estate),
 							  transition_capture,
 							  false);
@@ -3089,6 +3092,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple fdw_trigtuple,
 					 TupleTableSlot *newslot,
 					 List *recheckIndexes,
+					 bool 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, false, 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, bool recheckConstraints,
+					  Bitmapset *modifiedCols,
 					  TransitionCaptureState *transition_capture,
 					  bool is_crosspart_update)
 {
@@ -6064,6 +6069,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 	int			tgtype_level;
 	int			i;
 	Tuplestorestate *fdw_tuplestore = NULL;
+	bool		isChkConRechkQueued = false;
 
 	/*
 	 * Check state.  We use a normal test not Assert because it is possible to
@@ -6389,6 +6395,23 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 			}
 		}
 
+		/*
+		 * If the trigger is deferred check constraint recheck trigger, only
+		 * queue it if any of the check constraint was potentially violated
+		 * and no check constraint trigger is yet added(queued).
+		 */
+		if (trigger->tgfoid == F_CHECK_CONSTRAINT_RECHECK)
+		{
+			if (!recheckConstraints || isChkConRechkQueued)
+			{
+				continue;
+			}
+			else
+			{
+				isChkConRechkQueued = true;
+			}
+		}
+
 		/*
 		 * 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..6d6dd713c1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1734,12 +1734,14 @@ ExecutePlan(EState *estate,
 
 /*
  * ExecRelCheck --- check that tuple meets constraints for result relation
+ * and reset recheckConstraints to true in case of any constraint violation and
+ * 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, checkConstraintRecheck checkConstraint, bool *recheckConstraints)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	int			ncheck = rel->rd_att->constr->num_check;
@@ -1798,7 +1800,20 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 		 * ExecQual.
 		 */
 		if (!ExecCheck(checkconstr, econtext))
+		{
+
+			/*
+			 * If the constraint is deferrable and caller is
+			 * CHECK_RECHECK_ENABLED 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_RECHECK_ENABLED && check[i].ccdeferrable)
+			{
+				*recheckConstraints = true;
+			}
 			return check[i].ccname;
+		}
 	}
 
 	/* NULL result means no error */
@@ -1936,18 +1951,25 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
  * have been converted from the original input tuple after tuple routing.
  * 'resultRelInfo' is the final result relation, after tuple routing.
  */
-void
+bool
 ExecConstraints(ResultRelInfo *resultRelInfo,
-				TupleTableSlot *slot, EState *estate)
+				TupleTableSlot *slot, EState *estate, checkConstraintRecheck checkConstraint)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
 	TupleDesc	tupdesc = RelationGetDescr(rel);
 	TupleConstr *constr = tupdesc->constr;
 	Bitmapset  *modifiedCols;
+	bool		recheckConstraints = false;
 
 	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_RECHECK_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_RECHECK_EXISTING)
 	{
 		int			natts = tupdesc->natts;
 		int			attrChk;
@@ -2015,7 +2037,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	{
 		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 +2082,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 errtableconstraint(orig_rel, failed)));
 		}
 	}
+	return recheckConstraints;
 }
 
 /*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 81f27042bc..9a13aa94a7 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;
+		bool		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_RECHECK_ENABLED);
 		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;
+		bool		recheckConstraints = false;
 
 		/* 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_RECHECK_ENABLED);
 		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 5005d8c0d1..f67638f3ac 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;
+	bool		recheckConstraints;
 
 	/*
 	 * 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_RECHECK_ENABLED);
 
 		/*
 		 * Also check the tuple against the partition constraint, if there is
@@ -1162,6 +1163,7 @@ ExecInsert(ModifyTableContext *context,
 							 NULL,
 							 slot,
 							 NULL,
+							 false,
 							 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, false,
 							 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, false, 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, bool *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_RECHECK_ENABLED);
 
 	/*
 	 * 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, bool 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, false, NULL, true);
 }
 
 /* ----------------------------------------------------------------
@@ -2264,6 +2267,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	UpdateContext updateCxt = {0};
 	TM_Result	result;
+	bool		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};
+		bool		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/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 55c315f0e2..112609152d 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);
 
@@ -1417,6 +1419,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 			char	   *ccname = constr->check[ccnum].ccname;
 			char	   *ccbin = constr->check[ccnum].ccbin;
 			bool		ccnoinherit = constr->check[ccnum].ccnoinherit;
+			bool		ccdeferrable = constr->check[ccnum].ccdeferrable;
+			bool		ccdeferred = constr->check[ccnum].ccdeferred;
 			Node	   *ccbin_node;
 			bool		found_whole_row;
 			Constraint *n;
@@ -1446,6 +1450,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 			n->conname = pstrdup(ccname);
 			n->location = -1;
 			n->is_no_inherit = ccnoinherit;
+			n->deferrable = ccdeferrable;
+			n->initdeferred = ccdeferred;
 			n->raw_expr = NULL;
 			n->cooked_expr = nodeToString(ccbin_node);
 
@@ -3780,7 +3786,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..88d637df93 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4561,6 +4561,8 @@ CheckConstraintFetch(Relation relation)
 
 		check[found].ccvalid = conform->convalidated;
 		check[found].ccnoinherit = conform->connoinherit;
+		check[found].ccdeferrable = conform->condeferrable;
+		check[found].ccdeferred = conform->condeferred;
 		check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
 												  NameStr(conform->conname));
 
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index b4286cf922..87c18f6299 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -31,6 +31,8 @@ typedef struct ConstrCheck
 	char	   *ccbin;			/* nodeToString representation of expr */
 	bool		ccvalid;
 	bool		ccnoinherit;	/* this is a non-inheritable constraint */
+	bool		ccdeferrable;
+	bool		ccdeferred;
 } ConstrCheck;
 
 /* This structure contains constraints of a tuple */
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 51f7b12aa3..36aecd2be0 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -45,6 +45,8 @@ typedef struct CookedConstraint
 	int			inhcount;		/* number of times constraint is inherited */
 	bool		is_no_inherit;	/* constraint has local def and cannot be
 								 * inherited */
+	bool		is_deferrable;	/* is deferrable (only for CHECK) */
+	bool		is_deferred;	/* is deferred (only for CHECK) */
 } CookedConstraint;
 
 extern Relation heap_create(const char *relname,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..f935147718 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..d13908939e 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,
+								 bool 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,
+								 bool 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 c677e490d7..4dd36cbeba 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_RECHECK_DISABLED 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_RECHECK_ENABLED 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_RECHECK_EXISTING and this time conflicting latest live tuple
+ * will be revalidated.
+ */
+typedef enum checkConstraintRecheck
+{
+	CHECK_RECHECK_DISABLED,		/* Recheck of CHECK constraint is disabled, so
+								 * DEFERRED CHECK constraint will be
+								 * considered as non-deferrable check
+								 * constraint.  */
+	CHECK_RECHECK_ENABLED,		/* Recheck of CHECK constraint is enabled, so
+								 * CHECK constraint will be validated but
+								 * error will not be reported for deferred
+								 * CHECK constraint. */
+	CHECK_RECHECK_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 */
+}			checkConstraintRecheck;
+
 /*
  * 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 bool ExecConstraints(ResultRelInfo *resultRelInfo,
+							TupleTableSlot *slot, EState *estate, checkConstraintRecheck 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..2e3e1f9005 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -636,6 +636,104 @@ 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
+
+-- 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;
 -- 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..ea0281b39f 100644
--- a/src/test/regress/sql/constraints.sql
+++ b/src/test/regress/sql/constraints.sql
@@ -446,6 +446,105 @@ 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;
+
+-- 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;
+
 -- test naming a constraint in a partition when a conflict exists
 CREATE TABLE parted_fk_naming (
     id bigint NOT NULL default 1,
-- 
2.25.1

#6Dilip Kumar
dilipbalaut@gmail.com
In reply to: Himanshu Upadhyaya (#5)
Re: CHECK Constraint Deferrable

On Thu, Sep 7, 2023 at 1:25 PM Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Attached is v2 of the patch, rebased against the latest HEAD.

I have done some initial reviews, and here are my comments. More
detailed review later. Meanwhile, you can work on these comments and
fix all the cosmetics especially 80 characters per line

1.
+
+ (void) CreateTrigger(trigger, NULL, RelationGetRelid(rel),
+ InvalidOid, constrOid, InvalidOid, InvalidOid,
+ InvalidOid, NULL, true, false);

heap.c is calling CreateTrigger but the inclusion of
"commands/trigger.h" is missing.

2.
- if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
+ if ((failed = ExecRelCheck(resultRelInfo, slot, estate,
checkConstraint, &recheckConstraints)) != NULL && !recheckConstraints)

Why recheckConstraints need to get as output from ExecRelCheck? I
mean whether it will be rechecked or nor is already known by the
caller and
Whether the constraint failed or passed is known based on the return
value so why do you need to extra parameter here?

3.
-void
+bool
 ExecConstraints(ResultRelInfo *resultRelInfo,
- TupleTableSlot *slot, EState *estate)
+ TupleTableSlot *slot, EState *estate, checkConstraintRecheck checkConstraint)
 {
- if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
+ if ((failed = ExecRelCheck(resultRelInfo, slot, estate,
checkConstraint, &recheckConstraints)) != NULL && !recheckConstraints)

take care of postgres coding style and break line after 80
characters. Check other places as well in the patch.

4.
+ if (checkConstraint == CHECK_RECHECK_ENABLED && check[i].ccdeferrable)
+ {
+ *recheckConstraints = true;
+ }

Remove curly brackets around single-line block

5.
+typedef enum checkConstraintRecheck
+{
+ CHECK_RECHECK_DISABLED, /* Recheck of CHECK constraint is disabled, so
+ * DEFERRED CHECK constraint will be
+ * considered as non-deferrable check
+ * constraint.  */
+ CHECK_RECHECK_ENABLED, /* Recheck of CHECK constraint is enabled, so
+ * CHECK constraint will be validated but
+ * error will not be reported for deferred
+ * CHECK constraint. */
+ CHECK_RECHECK_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 */
+} checkConstraintRecheck;

I do not like the naming convention here, especially the words
RECHECK, DISABLE, and ENABLE. And also the name of the enum is a bit
off. We can name it more like a unique constraint
YES, PARTIAL, EXISTING

--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com

#7Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: Dilip Kumar (#6)
Re: CHECK Constraint Deferrable

On Fri, Sep 8, 2023 at 1:23 PM Dilip Kumar <dilipbalaut@gmail.com> wrote:

2.
- if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
+ if ((failed = ExecRelCheck(resultRelInfo, slot, estate,
checkConstraint, &recheckConstraints)) != NULL && !recheckConstraints)

Why recheckConstraints need to get as output from ExecRelCheck? I
mean whether it will be rechecked or nor is already known by the
caller and

Yes it will be known to the caller but ExecRelCheck will set this new
parameter only if any one of the constraint is defined as Deferrable (in
create table statement) and there is a potential constraint violation.

Whether the constraint failed or passed is known based on the return
value so why do you need to extra parameter here?

Because if normal CHECK constraint(non Deferrable) is violated, no need to

proceed with the insertion and in that case recheckConstraints will hold
"false" but if Deferrable check constraint is violated, we need to
revalidate the constraint at commit time and recheckConstraints will hold
"true".

5.
+typedef enum checkConstraintRecheck
+{
+ CHECK_RECHECK_DISABLED, /* Recheck of CHECK constraint is disabled, so
+ * DEFERRED CHECK constraint will be
+ * considered as non-deferrable check
+ * constraint.  */
+ CHECK_RECHECK_ENABLED, /* Recheck of CHECK constraint is enabled, so
+ * CHECK constraint will be validated but
+ * error will not be reported for deferred
+ * CHECK constraint. */
+ CHECK_RECHECK_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 */
+} checkConstraintRecheck;

I do not like the naming convention here, especially the words
RECHECK, DISABLE, and ENABLE. And also the name of the enum is a bit
off. We can name it more like a unique constraint
YES, PARTIAL, EXISTING

I can think of alternative ENUM name as "EnforceDeferredCheck" and member

as “CHECK_DEFERRED_YES”, “CHECK_DEFRRED_NO” and “CHECK_DEFERRED_EXISTING”.

Thoughts?
--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#8vignesh C
vignesh21@gmail.com
In reply to: Himanshu Upadhyaya (#5)
Re: CHECK Constraint Deferrable

On Thu, 7 Sept 2023 at 17:26, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Attached is v2 of the patch, rebased against the latest HEAD.

Thanks for working on this, few comments:
1) "CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t
text)" is crashing in windows, the same was noticed in CFBot too:
2023-09-11 08:11:36.585 UTC [58563][client backend]
[pg_regress/constraints][13/880:0] LOG: statement: CREATE TABLE
check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
2023-09-11 08:11:36.586 UTC [58560][client backend]
[pg_regress/inherit][15/391:0] LOG: statement: drop table c1;
../src/backend/commands/trigger.c:220:26: runtime error: member access
within null pointer of type 'struct CreateTrigStmt'
==58563==Using libbacktrace symbolizer.

The details of CFBot failure can be seen at [1]https://api.cirrus-ci.com/v1/artifact/task/4855966353588224/testrun/build-32/testrun/pg_upgrade/002_pg_upgrade/log/002_pg_upgrade_old_node.log

2) Alter of check constraint deferrable is not handled, is this intentional?
CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
postgres=# alter table check_constr_tbl alter constraint
check_constr_tbl_i_check not deferrable;
ERROR: constraint "check_constr_tbl_i_check" of relation
"check_constr_tbl" is not a foreign key constraint

3) Should we handle this scenario for domains too:
CREATE DOMAIN c1_check AS INT CHECK(VALUE > 10);
create table test(c1 c1_check);
alter domain c1_check ADD check (VALUE > 20) DEFERRABLE INITIALLY DEFERRED;

begin;
-- should this be deffered
insert into test values(19);
ERROR: value for domain c1_check violates check constraint "c1_check_check1"

4) There is one warning:
heap.c: In function ‘StoreRelCheck’:
heap.c:2178:24: warning: implicit declaration of function
‘CreateTrigger’ [-Wimplicit-function-declaration]
2178 | (void) CreateTrigger(trigger, NULL,
RelationGetRelid(rel),
| ^~~~~~~~~~~~~

5) This should be added to typedefs.list file:
+typedef enum checkConstraintRecheck
+{
+       CHECK_RECHECK_DISABLED,         /* Recheck of CHECK constraint
is disabled, so
+                                                                *
DEFERRED CHECK constraint will be
+                                                                *
considered as non-deferrable check
+                                                                *
constraint.  */
+       CHECK_RECHECK_ENABLED,          /* Recheck of CHECK constraint
is enabled, so
+                                                                *
CHECK constraint will be validated but
+                                                                *
error will not be reported for deferred
+                                                                *
CHECK constraint. */
+       CHECK_RECHECK_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 */
+}                      checkConstraintRecheck;

[1]: https://api.cirrus-ci.com/v1/artifact/task/4855966353588224/testrun/build-32/testrun/pg_upgrade/002_pg_upgrade/log/002_pg_upgrade_old_node.log

Regards,
Vignesh

#9vignesh C
vignesh21@gmail.com
In reply to: Himanshu Upadhyaya (#5)
Re: CHECK Constraint Deferrable

On Thu, 7 Sept 2023 at 17:26, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Attached is v2 of the patch, rebased against the latest HEAD.

Few issues:
1) Create domain fails but alter domain is successful, I feel we
should support create domain too:
postgres=# create domain d1 as int check(value<>0) deferrable;
ERROR: specifying constraint deferrability not supported for domains
postgres=# create domain d1 as int check(value<>0);
CREATE DOMAIN
postgres=# alter domain d1 add constraint con_2 check(value<>1) deferrable;
ALTER DOMAIN

2) I was not sure, if the error message change was intentional:
2a)
In Head:
CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER s1;
ERROR: misplaced DEFERRABLE clause
LINE 1: CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER...
^
postgres=# CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER s1;
ERROR: "t9" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

2b)
In Head:
postgres=# CREATE FOREIGN TABLE t2(a int CHECK(a<>0)) SERVER s1;
CREATE FOREIGN TABLE
postgres=# ALTER FOREIGN TABLE t2 ADD CONSTRAINT t2_chk_1 CHECK(a<>1)
DEFERRABLE;
ERROR: CHECK constraints cannot be marked DEFERRABLE

With patch:
postgres=# ALTER FOREIGN TABLE t8 ADD CONSTRAINT t8_chk_1 CHECK(a<>1)
DEFERRABLE;
ERROR: "t8" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

3) Insert check is not deferred to commit:
This insert check here is deferred to commit:
postgres=# CREATE TABLE tbl (i int ) partition by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
INSERT 0 1
postgres=*# commit;
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

But the check here is not deferred to commit:
postgres=# CREATE TABLE tbl (i int check(i<>0) DEFERRABLE) partition
by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

4) There is a new warning popping up now:
CREATE TABLE tbl_new_3 (i int check(i<>0)) partition by range (i);
CREATE FOREIGN TABLE ftbl_new_3 PARTITION OF tbl_new_3 FOR VALUES FROM
(40) TO (50) server s1;
postgres=# ALTER TABLE tbl_new_3 ADD CONSTRAINT tbl_new_3_chk
CHECK(i<>1) DEFERRABLE;
WARNING: unexpected pg_constraint record found for relation "tbl_new_3"
ERROR: "ftbl_new_3" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

Regards,
Vignesh

#10Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: vignesh C (#8)
Re: CHECK Constraint Deferrable

Thanks for the review comments.

On Tue, Sep 12, 2023 at 2:56 PM vignesh C <vignesh21@gmail.com> wrote:

On Thu, 7 Sept 2023 at 17:26, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Attached is v2 of the patch, rebased against the latest HEAD.

Thanks for working on this, few comments:
1) "CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t
text)" is crashing in windows, the same was noticed in CFBot too:
2023-09-11 08:11:36.585 UTC [58563][client backend]
[pg_regress/constraints][13/880:0] LOG: statement: CREATE TABLE
check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
2023-09-11 08:11:36.586 UTC [58560][client backend]
[pg_regress/inherit][15/391:0] LOG: statement: drop table c1;
../src/backend/commands/trigger.c:220:26: runtime error: member access
within null pointer of type 'struct CreateTrigStmt'
==58563==Using libbacktrace symbolizer.

Will Fix this in my next patch.

The details of CFBot failure can be seen at [1]

2) Alter of check constraint deferrable is not handled, is this
intentional?
CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
postgres=# alter table check_constr_tbl alter constraint
check_constr_tbl_i_check not deferrable;
ERROR: constraint "check_constr_tbl_i_check" of relation
"check_constr_tbl" is not a foreign key constraint

ALTER CONSTRAINT is currently only supported for FOREIGN KEY, it's even

not supported for UNIQUE constraint as below:
‘postgres[1271421]=#’CREATE TABLE unique_constr_tbl (i int unique
DEFERRABLE, t text);
CREATE TABLE
‘postgres[1271421]=#’\d unique_constr_tbl;
Table "public.unique_constr_tbl"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
t | text | | |
Indexes:
"unique_constr_tbl_i_key" UNIQUE CONSTRAINT, btree (i) DEFERRABLE
‘postgres[1271421]=#’alter table unique_constr_tbl alter constraint
unique_constr_tbl_i_key not deferrable;
ERROR: 42809: constraint "unique_constr_tbl_i_key" of relation
"unique_constr_tbl" is not a foreign key constraint
LOCATION: ATExecAlterConstraint, tablecmds.c:11183

I still need to understand the design restriction here, please let me know
if anyone is aware of this?
is it because of dependency on Indexes?

3) Should we handle this scenario for domains too:

CREATE DOMAIN c1_check AS INT CHECK(VALUE > 10);
create table test(c1 c1_check);
alter domain c1_check ADD check (VALUE > 20) DEFERRABLE INITIALLY DEFERRED;

begin;
-- should this be deffered
insert into test values(19);
ERROR: value for domain c1_check violates check constraint
"c1_check_check1"

Yes, thanks for notifying, I missed this for CREATE DOMAIN, will analyse

and include in next revision.

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#11Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: vignesh C (#9)
Re: CHECK Constraint Deferrable

On Thu, Sep 14, 2023 at 9:57 AM vignesh C <vignesh21@gmail.com> wrote:

3) Insert check is not deferred to commit:
This insert check here is deferred to commit:
postgres=# CREATE TABLE tbl (i int ) partition by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
INSERT 0 1
postgres=*# commit;
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

But the check here is not deferred to commit:
postgres=# CREATE TABLE tbl (i int check(i<>0) DEFERRABLE) partition
by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

I dont think it's a problem, in the second case there are two DEFERRABLE

CHECK constraints and you are marking one as DEFERRED but other one will be
INITIALLY IMMEDIATE. so we can use "SET CONSTRAINTS ALL DEFERRED;".
‘postgres[1271421]=#’CREATE TABLE tbl (i int check(i<>0) DEFERRABLE)
partition
‘...>’by range (i);
CREATE TABLE
‘postgres[1271421]=#’CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM
(0) TO (10);
CREATE TABLE
‘postgres[1271421]=#’CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM
(20) TO (30);
CREATE TABLE
‘postgres[1271421]=#’ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1)
DEFERRABLE;
ALTER TABLE
‘postgres[1271421]=#’\d tbl
Partitioned table "public.tbl"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Partition key: RANGE (i)
Check constraints:
"tbl_chk_1" CHECK (i <> 1) DEFERRABLE
"tbl_i_check" CHECK (i <> 0) DEFERRABLE
Number of partitions: 2 (Use \d+ to list them.)
‘postgres[1271421]=#’begin;
BEGIN
‘postgres[1271421]=#*’SET CONSTRAINTS ALL DEFERRED;
SET CONSTRAINTS
‘postgres[1271421]=#*’INSERT INTO tbl values (1);
INSERT 0 1
‘postgres[1271421]=#*’commit;
ERROR: 23514: new row for relation "tbl_1" violates check constraint
"tbl_chk_1"
DETAIL: Failing row contains (1).
SCHEMA NAME: public
TABLE NAME: tbl_1
CONSTRAINT NAME: tbl_chk_1
LOCATION: ExecConstraints, execMain.c:2077

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#12vignesh C
vignesh21@gmail.com
In reply to: Himanshu Upadhyaya (#11)
Re: CHECK Constraint Deferrable

On Thu, 14 Sept 2023 at 15:33, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

On Thu, Sep 14, 2023 at 9:57 AM vignesh C <vignesh21@gmail.com> wrote:

3) Insert check is not deferred to commit:
This insert check here is deferred to commit:
postgres=# CREATE TABLE tbl (i int ) partition by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
INSERT 0 1
postgres=*# commit;
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

But the check here is not deferred to commit:
postgres=# CREATE TABLE tbl (i int check(i<>0) DEFERRABLE) partition
by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

I dont think it's a problem, in the second case there are two DEFERRABLE CHECK constraints and you are marking one as DEFERRED but other one will be INITIALLY IMMEDIATE. so we can use "SET CONSTRAINTS ALL DEFERRED;".
‘postgres[1271421]=#’CREATE TABLE tbl (i int check(i<>0) DEFERRABLE) partition
‘...>’by range (i);
CREATE TABLE
‘postgres[1271421]=#’CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE
‘postgres[1271421]=#’CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
‘postgres[1271421]=#’ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
‘postgres[1271421]=#’\d tbl
Partitioned table "public.tbl"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Partition key: RANGE (i)
Check constraints:
"tbl_chk_1" CHECK (i <> 1) DEFERRABLE
"tbl_i_check" CHECK (i <> 0) DEFERRABLE
Number of partitions: 2 (Use \d+ to list them.)
‘postgres[1271421]=#’begin;
BEGIN
‘postgres[1271421]=#*’SET CONSTRAINTS ALL DEFERRED;
SET CONSTRAINTS
‘postgres[1271421]=#*’INSERT INTO tbl values (1);
INSERT 0 1
‘postgres[1271421]=#*’commit;
ERROR: 23514: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).
SCHEMA NAME: public
TABLE NAME: tbl_1
CONSTRAINT NAME: tbl_chk_1
LOCATION: ExecConstraints, execMain.c:2077

I think we should be able to defer one constraint like in the case of
foreign key constraint:
create table t1(c1 int primary key);
insert into t1 values(10);
create table t2(c1 int primary key);
insert into t2 values(10);
create table t3(c1 int, c2 int references t1(c1) deferrable, c3 int
references t2(c1) deferrable);

-- Set only one constraint as deferred
begin;
set CONSTRAINTS t3_c2_fkey deferred;
-- c2 column constraint is deferred, we need not set all constraints
deferred in this case, insert was successful
postgres=*# insert into t3 values(1,11,10);
INSERT 0 1
-- Throws error for the constraint that is not deferred
postgres=*# insert into t3 values(1,10,11);
ERROR: insert or update on table "t3" violates foreign key constraint
"t3_c3_fkey"
DETAIL: Key (c3)=(11) is not present in table "t2".

Thoughts?

Regards,
Vignesh

#13Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: vignesh C (#12)
Re: CHECK Constraint Deferrable

On Fri, 15 Sept 2023 at 08:00, vignesh C <vignesh21@gmail.com> wrote:

On Thu, 14 Sept 2023 at 15:33, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

On Thu, Sep 14, 2023 at 9:57 AM vignesh C <vignesh21@gmail.com> wrote:

postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

I dont think it's a problem, in the second case there are two DEFERRABLE CHECK constraints and you are marking one as DEFERRED but other one will be INITIALLY IMMEDIATE.

I think we should be able to defer one constraint like in the case of
foreign key constraint

Agreed. It should be possible to have a mix of deferred and immediate
constraint checks. In the example, the tbl_chk_1 is set deferred, but
it fails immediately, which is clearly not right.

I would say that it's reasonable to limit the scope of this patch to
table constraints only, and leave domain constraints to a possible
follow-up patch.

A few other review comments:

1. The following produces a WARNING (possibly the same issue already reported):

CREATE TABLE foo (a int, b int);
ALTER TABLE foo ADD CONSTRAINT a_check CHECK (a > 0);
ALTER TABLE foo ADD CONSTRAINT b_check CHECK (b > 0) DEFERRABLE;

WARNING: unexpected pg_constraint record found for relation "foo"

2. I think that equalTupleDescs() should compare the new fields, when
comparing the 2 sets of check constraints.

3. The constraint exclusion code in the planner should ignore
deferrable check constraints (see get_relation_constraints() in
src/backend/optimizer/util/plancat.c), otherwise it might incorrectly
exclude a relation on the basis of a constraint that is temporarily
violated, and return incorrect query results. For example:

CREATE TABLE foo (a int);
CREATE TABLE foo_c1 () INHERITS (foo);
CREATE TABLE foo_c2 () INHERITS (foo);
ALTER TABLE foo_c2 ADD CONSTRAINT cc CHECK (a != 5) INITIALLY DEFERRED;

BEGIN;
INSERT INTO foo_c2 VALUES (5);
SET LOCAL constraint_exclusion TO off;
SELECT * FROM foo WHERE a = 5;
SET LOCAL constraint_exclusion TO on;
SELECT * FROM foo WHERE a = 5;
ROLLBACK;

4. The code in MergeWithExistingConstraint() should prevent inherited
constraints being merged if their deferrable properties don't match
(as MergeConstraintsIntoExisting() does, since
constraints_equivalent() tests the deferrable fields). I.e., the
following should fail to merge the constraints, since they don't
match:

DROP TABLE IF EXISTS p,c;

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 c () INHERITS (p);
ALTER TABLE c ADD CONSTRAINT c1 CHECK (a > 0);
ALTER TABLE c ADD CONSTRAINT c2 CHECK (b > 0) DEFERRABLE;

I.e., that should produce an error, as happens if c is made to inherit
p *after* the constraints have been added.

5. Instead of just adding the new fields to the end of the ConstrCheck
struct, and to the end of lists of function parameters like
StoreRelCheck(), and other related code, it would be more logical to
put them immediately before the valid/invalid entries, to match the
order of constraint properties in pg_constraint, and functions like
CreateConstraintEntry().

Regards,
Dean

#14Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: vignesh C (#8)
Re: CHECK Constraint Deferrable

On Tue, Sep 12, 2023 at 2:56 PM vignesh C <vignesh21@gmail.com> wrote:

On Thu, 7 Sept 2023 at 17:26, Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Attached is v2 of the patch, rebased against the latest HEAD.

Thanks for working on this, few comments:
1) "CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t
text)" is crashing in windows, the same was noticed in CFBot too:
2023-09-11 08:11:36.585 UTC [58563][client backend]
[pg_regress/constraints][13/880:0] LOG: statement: CREATE TABLE
check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
2023-09-11 08:11:36.586 UTC [58560][client backend]
[pg_regress/inherit][15/391:0] LOG: statement: drop table c1;
../src/backend/commands/trigger.c:220:26: runtime error: member access
within null pointer of type 'struct CreateTrigStmt'
==58563==Using libbacktrace symbolizer.

The details of CFBot failure can be seen at [1]

I have tried it with my latest patch on windows environment and not

getting any crash with the above statement, will do further analysis if
this patch also has the same issue.

2) Alter of check constraint deferrable is not handled, is this
intentional?
CREATE TABLE check_constr_tbl (i int CHECK(i<>0) DEFERRABLE, t text);
postgres=# alter table check_constr_tbl alter constraint
check_constr_tbl_i_check not deferrable;
ERROR: constraint "check_constr_tbl_i_check" of relation
"check_constr_tbl" is not a foreign key constraint

This is not allowed for any constraint type but FOREIGN key. I am not very

sure about if there is any limitation with this so wanted to take opinion
from other hackers on this.

3) Should we handle this scenario for domains too:
CREATE DOMAIN c1_check AS INT CHECK(VALUE > 10);
create table test(c1 c1_check);
alter domain c1_check ADD check (VALUE > 20) DEFERRABLE INITIALLY DEFERRED;

begin;
-- should this be deffered
insert into test values(19);
ERROR: value for domain c1_check violates check constraint
"c1_check_check1"

We are planning to have a follow-up patch once this initial patch is

committed.

4) There is one warning:
heap.c: In function ‘StoreRelCheck’:
heap.c:2178:24: warning: implicit declaration of function
‘CreateTrigger’ [-Wimplicit-function-declaration]
2178 | (void) CreateTrigger(trigger, NULL,
RelationGetRelid(rel),
|

Fixed in V3 patch.

^~~~~~~~~~~~~

5) This should be added to typedefs.list file:
+typedef enum checkConstraintRecheck
+{
+       CHECK_RECHECK_DISABLED,         /* Recheck of CHECK constraint
is disabled, so
+                                                                *
DEFERRED CHECK constraint will be
+                                                                *
considered as non-deferrable check
+                                                                *
constraint.  */
+       CHECK_RECHECK_ENABLED,          /* Recheck of CHECK constraint
is enabled, so
+                                                                *
CHECK constraint will be validated but
+                                                                *
error will not be reported for deferred
+                                                                *
CHECK constraint. */
+       CHECK_RECHECK_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 */
+}                      checkConstraintRecheck;

Fixed in V3 patch.

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#15Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: vignesh C (#9)
Re: CHECK Constraint Deferrable

On Thu, Sep 14, 2023 at 9:57 AM vignesh C <vignesh21@gmail.com> wrote:

2) I was not sure, if the error message change was intentional:
2a)
In Head:
CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER s1;
ERROR: misplaced DEFERRABLE clause
LINE 1: CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER...
^
postgres=# CREATE FOREIGN TABLE t9(a int CHECK(a<>0) DEFERRABLE) SERVER s1;
ERROR: "t9" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

2b)
In Head:
postgres=# CREATE FOREIGN TABLE t2(a int CHECK(a<>0)) SERVER s1;
CREATE FOREIGN TABLE
postgres=# ALTER FOREIGN TABLE t2 ADD CONSTRAINT t2_chk_1 CHECK(a<>1)
DEFERRABLE;
ERROR: CHECK constraints cannot be marked DEFERRABLE

With patch:
postgres=# ALTER FOREIGN TABLE t8 ADD CONSTRAINT t8_chk_1 CHECK(a<>1)
DEFERRABLE;
ERROR: "t8" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

We are creating a constraint trigger for DEFERRED check constraint and as

per implementation of FOREIGN table we are restricting to have a constraint
trigger. I need to do more analysis before reaching to any conclusion, I
think we can restrict this gram.y itself.

3) Insert check is not deferred to commit:
This insert check here is deferred to commit:
postgres=# CREATE TABLE tbl (i int ) partition by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
INSERT 0 1
postgres=*# commit;
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

But the check here is not deferred to commit:
postgres=# CREATE TABLE tbl (i int check(i<>0) DEFERRABLE) partition
by range (i);
CREATE TABLE tbl_1 PARTITION OF tbl FOR VALUES FROM (0) TO (10);
CREATE TABLE tbl_2 PARTITION OF tbl FOR VALUES FROM (20) TO (30);
CREATE TABLE
CREATE TABLE
CREATE TABLE
postgres=# ALTER TABLE tbl ADD CONSTRAINT tbl_chk_1 CHECK(i<>1) DEFERRABLE;
ALTER TABLE
postgres=# begin;
BEGIN
postgres=*# SET CONSTRAINTS tbl_chk_1 DEFERRED;
SET CONSTRAINTS
postgres=*# INSERT INTO tbl values (1);
ERROR: new row for relation "tbl_1" violates check constraint "tbl_chk_1"
DETAIL: Failing row contains (1).

Fixed in V3 patch.

4) There is a new warning popping up now:
CREATE TABLE tbl_new_3 (i int check(i<>0)) partition by range (i);
CREATE FOREIGN TABLE ftbl_new_3 PARTITION OF tbl_new_3 FOR VALUES FROM
(40) TO (50) server s1;
postgres=# ALTER TABLE tbl_new_3 ADD CONSTRAINT tbl_new_3_chk
CHECK(i<>1) DEFERRABLE;
WARNING: unexpected pg_constraint record found for relation "tbl_new_3"
ERROR: "ftbl_new_3" is a foreign table
DETAIL: Foreign tables cannot have constraint triggers.

Fixed in V3 patch.

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#16Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: Dean Rasheed (#13)
Re: CHECK Constraint Deferrable

On Tue, Sep 19, 2023 at 4:14 PM Dean Rasheed <dean.a.rasheed@gmail.com>
wrote:

I think we should be able to defer one constraint like in the case of
foreign key constraint

Agreed. It should be possible to have a mix of deferred and immediate
constraint checks. In the example, the tbl_chk_1 is set deferred, but
it fails immediately, which is clearly not right.

Fixed in V3 patch.

I would say that it's reasonable to limit the scope of this patch to
table constraints only, and leave domain constraints to a possible
follow-up patch.

Sure, Agree.

A few other review comments:

1. The following produces a WARNING (possibly the same issue already
reported):

CREATE TABLE foo (a int, b int);
ALTER TABLE foo ADD CONSTRAINT a_check CHECK (a > 0);
ALTER TABLE foo ADD CONSTRAINT b_check CHECK (b > 0) DEFERRABLE;

WARNING: unexpected pg_constraint record found for relation "foo"

fixed in V3 patch.

2. I think that equalTupleDescs() should compare the new fields, when
comparing the 2 sets of check constraints.

Fixed in V3 patch.

3. The constraint exclusion code in the planner should ignore
deferrable check constraints (see get_relation_constraints() in
src/backend/optimizer/util/plancat.c), otherwise it might incorrectly
exclude a relation on the basis of a constraint that is temporarily
violated, and return incorrect query results. For example:

CREATE TABLE foo (a int);
CREATE TABLE foo_c1 () INHERITS (foo);
CREATE TABLE foo_c2 () INHERITS (foo);
ALTER TABLE foo_c2 ADD CONSTRAINT cc CHECK (a != 5) INITIALLY DEFERRED;

BEGIN;
INSERT INTO foo_c2 VALUES (5);
SET LOCAL constraint_exclusion TO off;
SELECT * FROM foo WHERE a = 5;
SET LOCAL constraint_exclusion TO on;
SELECT * FROM foo WHERE a = 5;
ROLLBACK;

Fixed in V3 patch.

4. The code in MergeWithExistingConstraint() should prevent inherited
constraints being merged if their deferrable properties don't match
(as MergeConstraintsIntoExisting() does, since
constraints_equivalent() tests the deferrable fields). I.e., the
following should fail to merge the constraints, since they don't
match:

DROP TABLE IF EXISTS p,c;

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 c () INHERITS (p);
ALTER TABLE c ADD CONSTRAINT c1 CHECK (a > 0);
ALTER TABLE c ADD CONSTRAINT c2 CHECK (b > 0) DEFERRABLE;

I.e., that should produce an error, as happens if c is made to inherit
p *after* the constraints have been added.

Fixed in V3 patch.

5. Instead of just adding the new fields to the end of the ConstrCheck
struct, and to the end of lists of function parameters like
StoreRelCheck(), and other related code, it would be more logical to
put them immediately before the valid/invalid entries, to match the
order of constraint properties in pg_constraint, and functions like
CreateConstraintEntry().

Fixed in V3 patch.

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

#17Himanshu Upadhyaya
upadhyaya.himanshu@gmail.com
In reply to: Himanshu Upadhyaya (#16)
1 attachment(s)
Re: CHECK Constraint Deferrable

On Mon, Oct 2, 2023 at 8:31 PM Himanshu Upadhyaya <
upadhyaya.himanshu@gmail.com> wrote:

V3 patch attached.

--
Regards,
Himanshu Upadhyaya
EnterpriseDB: http://www.enterprisedb.com

Attachments:

v3-0001-Implementation-of-CHECK-Constraint-to-make-it.patchapplication/octet-stream; name=v3-0001-Implementation-of-CHECK-Constraint-to-make-it.patchDownload
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

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Himanshu Upadhyaya (#17)
Re: CHECK Constraint Deferrable

Himanshu Upadhyaya <upadhyaya.himanshu@gmail.com> writes:

V3 patch attached.

Sorry for not weighing in on this before, but ... is this a feature
we want at all? We are very clear in the existing docs that CHECK
conditions must be immutable [1]See Note at the bottom of "5.4.1. Check Constraints" here: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS, and that's not something we can
easily relax because if they are not then it's unclear when we need
to recheck them to ensure they stay satisfied. But here we have a
feature whose only possible use is with constraints that *aren't*
immutable; else we might as well just check them immediately.
So that gives rise to a bunch of subtle questions about exactly what
properties a user-written constraint would need to have to guarantee
sane semantics given this implementation. Can we define what those
properties are, or what the ensuing semantic guarantees are exactly?
Can we explain those things clearly enough that the average user would
have a shot at writing a valid deferred constraint? Is a deferred
constraint having those properties likely to be actually useful?

I don't know the answers to these questions, but it troubles me a
lot that zero consideration appears to have been given to them.
I do not think we should put more effort into this patch unless
satisfactory answers are forthcoming.

regards, tom lane

[1]: See Note at the bottom of "5.4.1. Check Constraints" here: https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS
https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS

#19David G. Johnston
david.g.johnston@gmail.com
In reply to: Tom Lane (#18)
Re: CHECK Constraint Deferrable

On Mon, Oct 2, 2023 at 12:25 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Himanshu Upadhyaya <upadhyaya.himanshu@gmail.com> writes:

V3 patch attached.

Sorry for not weighing in on this before, but ... is this a feature
we want at all? We are very clear in the existing docs that CHECK
conditions must be immutable [1], and that's not something we can
easily relax because if they are not then it's unclear when we need
to recheck them to ensure they stay satisfied.

Agreed. I'm not sold on conforming to the standard being an appropriate
ideal here. Either we already don't because our check constraints are
immutable, or I'm missing what use case the committee had in mind when they
designed this feature. In any case, its absence doesn't seem that sorely
missed, and the OP's only actual example would require relaxing the
immutable property which I disagree with. We have deferrable triggers to
serve that posited use case.

David J.

#20Vik Fearing
vik@postgresfriends.org
In reply to: Tom Lane (#18)
Re: CHECK Constraint Deferrable

On 10/2/23 21:25, Tom Lane wrote:

Himanshu Upadhyaya <upadhyaya.himanshu@gmail.com> writes:

V3 patch attached.

Sorry for not weighing in on this before, but ... is this a feature
we want at all?

For standards conformance, I vote yes.

We are very clear in the existing docs that CHECK
conditions must be immutable [1], and that's not something we can
easily relax because if they are not then it's unclear when we need
to recheck them to ensure they stay satisfied.

That is what the *user* documentation says, but we both know it isn't true.

Here is a short conversation you and I had about five years ago where
you defended the non-immutability of CHECK constraints:
/messages/by-id/12539.1544107316@sss.pgh.pa.us

But here we have a
feature whose only possible use is with constraints that *aren't*
immutable; else we might as well just check them immediately.

I disagree with this. The whole point of deferring constraints is to be
able to do some cleanup before the consistency is checked.

So that gives rise to a bunch of subtle questions about exactly what
properties a user-written constraint would need to have to guarantee
sane semantics given this implementation. Can we define what those
properties are, or what the ensuing semantic guarantees are exactly?
Can we explain those things clearly enough that the average user would
have a shot at writing a valid deferred constraint?

A trivial example is CHECK (c IS NOT NULL) which, according to the
standard, is the only way to check for such a condition. The NOT NULL
syntax is explicitly translated to that by 11.4 <column definition> SR
17.a. We implement it a bit differently, but that does not negate the
usefulness of being able to defer it. In fact, all of the work Álvaro
is currently doing is mainly (or even fully) to be able to defer such a
constraint.

Is a deferred
constraint having those properties likely to be actually useful?

I believe the answer is yes.
--
Vik Fearing

#21Andreas Joseph Krogh
andreas@visena.com
In reply to: Dilip Kumar (#2)
Re: CHECK Constraint Deferrable

På fredag 07. juli 2023 kl. 13:50:44, skrev Dilip Kumar <dilipbalaut@gmail.com
<mailto:dilipbalaut@gmail.com>>:
On Wed, Jul 5, 2023 at 3:08 PM Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Hi,

Currently, there is no support for CHECK constraint DEFERRABLE in a create

table statement.

SQL standard specifies that CHECK constraint can be defined as DEFERRABLE.

I think this is a valid argument that this is part of SQL standard so
it would be good addition to PostgreSQL. So +1 for the feature.

But I am wondering whether there are some real-world use cases for
deferred CHECK/NOT NULL constraints? I mean like for foreign key
constraints if there is a cyclic dependency between two tables then
deferring the constraint is the simplest way to insert without error.

The real-world use case, at least for me, is when using an ORM. For large
object-graphs ORMs have a tendency to INSERT first with NULLs then UPDATE the
“NOT NULLs” later.

“Rewrite the ORM” is not an option for most of us…

--

Andreas Joseph Krogh

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vik Fearing (#20)
Re: CHECK Constraint Deferrable

Vik Fearing <vik@postgresfriends.org> writes:

On 10/2/23 21:25, Tom Lane wrote:

Sorry for not weighing in on this before, but ... is this a feature
we want at all?

For standards conformance, I vote yes.

Only if we can actually implement it in a defensible way, which this
patch is far short of accomplishing.

We are very clear in the existing docs that CHECK
conditions must be immutable [1],

That is what the *user* documentation says, but we both know it isn't true.
Here is a short conversation you and I had about five years ago where
you defended the non-immutability of CHECK constraints:
/messages/by-id/12539.1544107316@sss.pgh.pa.us

What I intended to defend was not *checking* immutability strictly.
Our CHECK constraint implementation is based very much on the assumption
that the constraints are immutable, and nobody has proposed that we
try to remove that assumption AFAIR. So I think the docs are fine
as-is; anybody who wants to get into monotonically-weakening constraints
is probably smart enough to work out for themselves whether it will
fly or not.

So my problem with this patch is that it does nothing about that
assumption, and yet the feature it adds seems useless without
weakening the assumption. So what weaker assumption could we
make, and how would we modify the when-to-check rules to match
that, and what would it cost us in performance? Without good
answers to those questions, this patch is just a facade.

I disagree with this. The whole point of deferring constraints is to be
able to do some cleanup before the consistency is checked.

What cleanup would you need that couldn't be performed beforehand
(e.g. in a BEFORE INSERT/UPDATE trigger)? All the practical
examples that occur to me involve cross-row conditions, which
CHECK is unsuitable to enforce --- at least, without doing a
thorough implementation rethink.

I continue to assert that basing this feature on the current
CHECK implementation will produce nothing but a toy feature,
that's not only of little practical use but will be an active
foot-gun for people who expect it to do more than it can.

regards, tom lane

#23David G. Johnston
david.g.johnston@gmail.com
In reply to: Andreas Joseph Krogh (#21)
Re: CHECK Constraint Deferrable

On Monday, October 2, 2023, Andreas Joseph Krogh <andreas@visena.com> wrote:

På fredag 07. juli 2023 kl. 13:50:44, skrev Dilip Kumar <
dilipbalaut@gmail.com>:

On Wed, Jul 5, 2023 at 3:08 PM Himanshu Upadhyaya
<upadhyaya.himanshu@gmail.com> wrote:

Hi,

Currently, there is no support for CHECK constraint DEFERRABLE in a

create table statement.

SQL standard specifies that CHECK constraint can be defined as

DEFERRABLE.

I think this is a valid argument that this is part of SQL standard so
it would be good addition to PostgreSQL. So +1 for the feature.

But I am wondering whether there are some real-world use cases for
deferred CHECK/NOT NULL constraints? I mean like for foreign key
constraints if there is a cyclic dependency between two tables then
deferring the constraint is the simplest way to insert without error.

The real-world use case, at least for me, is when using an ORM. For large
object-graphs ORMs have a tendency to INSERT first with NULLs then UPDATE
the “NOT NULLs” later.

“Rewrite the ORM” is not an option for most of us…

Between this and Vik comment it sounds like we should probably require a
patch in this area to solve both the not null and check constraint deferral
omissions then, not just one of them (alternatively, let’s solve the not
null one first).

David J.

#24Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#18)
Re: CHECK Constraint Deferrable

On Mon, Oct 2, 2023 at 10:25 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

But here we have a
feature whose only possible use is with constraints that *aren't*
immutable; else we might as well just check them immediately.

I'm a little bit confused by this whole discussion because surely this
statement is just completely false. The example in the original post
demonstrates that clearly.

The use case for a deferred check constraint is exactly the same as
the use case for a deferred foreign key constraint or a deferred
uniqueness constraint, which is that you might have a constraint that
will be temporarily false while the transaction is in progress, but
true by the time the transaction actually commits, and you might like
the transaction to succeed instead of failing in such a case. You seem
to be imagining that the constraint itself might be returning mutable
answers on the same inputs, but that's not what this is about at all.

I'm not here - at least, not right now - to take a position on whether
the patch itself is any good.

--
Robert Haas
EDB: http://www.enterprisedb.com

#25Robert Haas
robertmhaas@gmail.com
In reply to: David G. Johnston (#23)
Re: CHECK Constraint Deferrable

On Tue, Oct 3, 2023 at 10:05 AM David G. Johnston
<david.g.johnston@gmail.com> wrote:

The real-world use case, at least for me, is when using an ORM. For large object-graphs ORMs have a tendency to INSERT first with NULLs then UPDATE the “NOT NULLs” later.

“Rewrite the ORM” is not an option for most of us…

Between this and Vik comment it sounds like we should probably require a patch in this area to solve both the not null and check constraint deferral omissions then, not just one of them (alternatively, let’s solve the not null one first).

I have a couple of problems with this comment:

1. I don't know which of Vik's comments you're talking about here, but
it seems to me that Vik is generally in favor of this feature, so I'm
a bit surprised to hear that one of his comments led you to think that
it should be burdened with additional requirements.

2. I don't think it's a good idea for the same patch to try to solve
two problems unless they are so closely related that solving one
without solving the other is not sensible. It is a good policy for the
community to accept incremental progress provided it doesn't break
things along the way. Smaller patches are way easier to get committed,
and then we get some of the feature sooner instead of all of it some
more distant point in the future or never. Furthemore, forcing
additional requirements onto patch submitters as a condition of patch
acceptance is extremely demoralizing to submitters, and we should not
do it without an excellent reason.

Mind you, I'm not against this patch handling both CHECK and NOT NULL
constraints if that's the most sensible way forward, especially in
view of Álvaro's recent work in that area. But it sort of sounds like
you're just trying to sink the patch despite it being a feature that
is both in the SQL standard and has real use cases which have been
mentioned on the thread, and I don't really like that. In the interest
of full disclosure, I do work at the same company as Dilip and
Himanshu, so I might be biased. But I feel like I would be in favor of
this feature no matter who proposed it, as long as it was
well-implemented. It's always struck me as odd that we allow deferring
some types of constraints but not others, and I don't understand why
we'd want to block closing that gap unless there is some concrete
downside to so doing.

--
Robert Haas
EDB: http://www.enterprisedb.com

#26David G. Johnston
david.g.johnston@gmail.com
In reply to: Robert Haas (#25)
Re: CHECK Constraint Deferrable

On Mon, Oct 9, 2023 at 1:27 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Oct 3, 2023 at 10:05 AM David G. Johnston
<david.g.johnston@gmail.com> wrote:

The real-world use case, at least for me, is when using an ORM. For

large object-graphs ORMs have a tendency to INSERT first with NULLs then
UPDATE the “NOT NULLs” later.

“Rewrite the ORM” is not an option for most of us…

Between this and Vik comment it sounds like we should probably require a

patch in this area to solve both the not null and check constraint deferral
omissions then, not just one of them (alternatively, let’s solve the not
null one first).

I have a couple of problems with this comment:

1. I don't know which of Vik's comments you're talking about here, but
it seems to me that Vik is generally in favor of this feature, so I'm
a bit surprised to hear that one of his comments led you to think that
it should be burdened with additional requirements.

Specifically, Vik commented that the standard requires implementing NOT
NULL as a check constraint and thus needs to allow for deferrable check
constraints in order for not null checks to be deferrable. I agree fully
that deferring a not null check makes for an excellent use case. But we've
also decided to make NOT NULL its own thing, contrary to the standard.
Thus my understanding for why this behavior is standard mandated is that it
is to allow for deferrable not null constraints and thus our goal should be
to make our not null constraints deferrable.

The only other example case of wanting a deferrable check constraint
involved the usage of a function that we expressly prohibit as a check
constraint. The argument, which I weakly support, is that if our adding
deferrable check constraints increases the frequency of such functions
being created and used by our users, then we should continue to prohibit
such deferrability and require those users to properly implement triggers
which can then be deferred. With a deferrable not null constraint any
other reasonable check constraints can simply evaluate to null during the
period where they should be deferred - because their column inputs are
deferred nulls - and then we be fully evaluated when the inputs ultimately
end up non-null.

2. I don't think it's a good idea for the same patch to try to solve

two problems unless they are so closely related that solving one
without solving the other is not sensible.

A NOT NULL constraint apparently is just a special case of a check
constraint which seems closely related enough to match your definition.

But I guess you are right, I was trying to say no to this patch, and yes to
the not null deferral idea, without being so explicit and final about it.

While the coders are welcome to work on whatever they wish, the effort
spent on this just doesn't seem that valuable compared to what is already
in the queue being worked on needing reviews and commits. I can live with a
gap in our standards conformance here since I haven't observed any uses
cases that are impossible to accomplish except by adding this specific
feature which would only cover NOT NULL constraints if the syntactical form
for creating them were not used (which I suppose if we fail to provide NOT
NULL DEFERRABLE that would argue for at least giving this work-around...)

David J.

#27Robert Haas
robertmhaas@gmail.com
In reply to: David G. Johnston (#26)
Re: CHECK Constraint Deferrable

On Mon, Oct 9, 2023 at 5:07 PM David G. Johnston
<david.g.johnston@gmail.com> wrote:

2. I don't think it's a good idea for the same patch to try to solve
two problems unless they are so closely related that solving one
without solving the other is not sensible.

A NOT NULL constraint apparently is just a special case of a check constraint which seems closely related enough to match your definition.

Yes, that might be true. I suppose I'd like to hear from the patch
author(s) about that. I'm somewhat coming around to your idea that
maybe both should be covered together, but I'm not the one writing the
patch.

But I guess you are right, I was trying to say no to this patch, and yes to the not null deferral idea, without being so explicit and final about it.

But this, I dislike, for reasons which I'm sure you can appreciate. As
you say, people are free to choose their own development priorities. I
don't need this feature for anything either, personally, but my need
or lack of it for some particular feature doesn't define the objective
usefulness thereof. And to be honest, if I were trying to step back
from my personal needs, I'd say this seems likely to be more useful
than 75% of what's in the CommitFest. Your judgement can be different
and that's fine too, but I think the argument for calling this useless
is weak, especially given that several people have already mentioned
ways that they would like to use it.

--
Robert Haas
EDB: http://www.enterprisedb.com

#28Vik Fearing
vik@postgresfriends.org
In reply to: Robert Haas (#27)
Re: CHECK Constraint Deferrable

On 10/10/23 15:12, Robert Haas wrote:

On Mon, Oct 9, 2023 at 5:07 PM David G. Johnston
<david.g.johnston@gmail.com> wrote:

2. I don't think it's a good idea for the same patch to try to solve
two problems unless they are so closely related that solving one
without solving the other is not sensible.

A NOT NULL constraint apparently is just a special case of a check constraint which seems closely related enough to match your definition.

Yes, that might be true. I suppose I'd like to hear from the patch
author(s) about that. I'm somewhat coming around to your idea that
maybe both should be covered together, but I'm not the one writing the
patch.

Álvaro Herrera has put (and is still putting) immense effort into
turning NOT NULL into a CHECK constraint.

Honestly, I don't see why the two patches need to be combined.
--
Vik Fearing