extension patch of CREATE OR REPLACE TRIGGER

Started by Osumi, Takamichialmost 7 years ago48 messages
#1Osumi, Takamichi
osumi.takamichi@jp.fujitsu.com
1 attachment(s)

Dear hackers

Hi there !
One past thread about introducing CREATE OR REPLACE TRIGGER into the syntax
had stopped without complete discussion in terms of LOCK level.

The past thread is this. I'd like to inherit this one.
/messages/by-id/0B4917A40C80E34BBEC4BE1A7A9AB7E276F5D9@g01jpexmbkw05
Mr. Tom Lane mentioned that this change requires really careful study in this thread.

First of all, please don't forget I don't talk about DO CLAUSE in this thread.
Secondly, Mr. Surafel Temesgen pointed out a bug but it doesn't appear.

Anyway, let's go back to the main topic.
From my perspective, how CREATE OR REPLACE TRIGGER works is,
when there is no counterpart replaced by a new trigger,
CREATE TRIGGER is processed with SHARE ROW EXCLUSIVE LOCK as usual.

On the other hand, when there's,
REPLACE TRIGGER procedure is executed with ACCESS EXCLUSIVE LOCK.

This feeling comes from my idea
that acquiring ACCESS EXCLUSIVE LOCK when replacing trigger occurs
provides data consistency between transactions and protects concurrent pg_dump.

In order to make this come true, as the first step,
I've made a patch to add CREATE OR REPLACE TRIGGER with some basic tests in triggers.sql.

Yet, I'm still wondering which part of LOCK level in this patch should be raised to ACCESS EXCLUSIVE LOCK.
Could anyone give me an advise about
how to protect the process of trigger replacement in the way I suggested above ?

--------------------
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v01.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v01.patchDownload
diff -ur b/doc/src/sgml/ref/create_trigger.sgml a/doc/src/sgml/ref/create_trigger.sgml
--- b/doc/src/sgml/ref/create_trigger.sgml	2019-02-28 06:04:32.776078441 +0000
+++ a/doc/src/sgml/ref/create_trigger.sgml	2019-02-28 06:38:01.628078441 +0000
@@ -26,7 +26,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
Binary files b/.git/index and a/.git/index differ
diff -ur b/src/backend/catalog/heap.c a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c	2019-02-28 06:04:32.878078441 +0000
+++ a/src/backend/catalog/heap.c	2019-02-28 06:18:33.915078441 +0000
@@ -2375,7 +2375,8 @@
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
-							  is_internal); /* internally constructed? */
+							  is_internal, /* internally constructed? */
+							  InvalidOid);							  
 
 	pfree(ccbin);
 
diff -ur b/src/backend/catalog/index.c a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c	2019-02-28 06:04:32.879078441 +0000
+++ a/src/backend/catalog/index.c	2019-02-28 06:18:29.702078441 +0000
@@ -1331,7 +1331,8 @@
 								   islocal,
 								   inhcount,
 								   noinherit,
-								   is_internal);
+								   is_internal,
+								   InvalidOid);
 
 	/*
 	 * Register the index as internally dependent on the constraint.
diff -ur b/src/backend/catalog/pg_constraint.c a/src/backend/catalog/pg_constraint.c
--- b/src/backend/catalog/pg_constraint.c	2019-02-28 06:04:32.882078441 +0000
+++ a/src/backend/catalog/pg_constraint.c	2019-02-28 06:19:43.451078441 +0000
@@ -76,10 +76,11 @@
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
-					  bool is_internal)
+					  bool is_internal,
+					  Oid existing_constraint_oid)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -92,7 +93,11 @@
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc conscan;
+	ScanKeyData skey[2];
+	HeapTuple	tuple;
+	bool		replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -164,9 +169,11 @@
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if(!existing_constraint_oid){
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -220,9 +227,44 @@
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
-
-	CatalogTupleInsert(conDesc, tup);
+	if (OidIsValid(existing_constraint_oid)){
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}else{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff -ur b/src/backend/commands/tablecmds.c a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c	2019-02-28 06:04:32.905078441 +0000
+++ a/src/backend/commands/tablecmds.c	2019-02-28 06:12:34.362078441 +0000
@@ -7633,7 +7633,8 @@
 									  true, /* islocal */
 									  0,	/* inhcount */
 									  connoinherit,	/* conNoInherit */
-									  false);	/* is_internal */
+									  false,	/* is_internal */
+									  InvalidOid);
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
 	/*
@@ -8016,7 +8017,7 @@
 								  NULL,
 								  NULL,
 								  false,
-								  1, false, true);
+								  1, false, true, InvalidOid);
 		subclone = lappend_oid(subclone, constrOid);
 
 		/* Set up partition dependencies for the new constraint */
diff -ur b/src/backend/commands/trigger.c a/src/backend/commands/trigger.c
--- b/src/backend/commands/trigger.c	2019-02-28 06:04:32.907078441 +0000
+++ a/src/backend/commands/trigger.c	2019-02-28 06:14:16.578078441 +0000
@@ -29,6 +29,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -178,7 +179,7 @@
 	HeapTuple	tuple;
 	Oid			fargtypes[1];	/* dummy */
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -188,6 +189,13 @@
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
 
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
 	else
@@ -666,6 +674,59 @@
 	}
 
 	/*
+	 * Generate the trigger's OID now, so that we can use it in the name if
+	 * needed.
+	 */
+	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+								NULL, 1, &key);
+	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+	{
+		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+		if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+		{
+			// trigoid = HeapTupleGetOid(tuple); // raw code
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			trigger_exists = true;
+			break;
+		}
+	}
+	systable_endscan(tgscan);
+
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	
+	/* Replacement between normal triggers and constraint triggers are restricted */
+	if (stmt->replace && trigger_exists)
+	{
+		if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+		else if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+	}
+	else if (trigger_exists && !isInternal)
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DUPLICATE_OBJECT),
+			errmsg("trigger \"%s\" for relation \"%s\" already exists",
+			stmt->trigname, RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Find and validate the trigger function.
 	 */
 	if (!OidIsValid(funcoid))
@@ -755,19 +816,11 @@
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* isnoinherit */
-											  isInternal);	/* is_internal */
+											  isInternal, /* is_internal */
+											  existing_constraint_oid);
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -785,37 +838,6 @@
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -942,12 +964,51 @@
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	heap_freetuple(tuple);
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+	
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -990,6 +1051,15 @@
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff -ur b/src/backend/commands/typecmds.c a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c	2019-02-28 06:04:32.909078441 +0000
+++ a/src/backend/commands/typecmds.c	2019-02-28 06:14:54.554078441 +0000
@@ -3167,7 +3167,8 @@
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
-							  false);	/* is_internal */
+							  false,	/* is_internal */
+							  InvalidOid);
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
 
diff -ur b/src/backend/parser/gram.y a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y	2019-02-28 06:04:32.977078441 +0000
+++ a/src/backend/parser/gram.y	2019-02-28 06:09:25.615078441 +0000
@@ -5271,48 +5271,50 @@
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff -ur b/src/include/catalog/pg_constraint.h a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h	2019-02-28 06:04:33.462078441 +0000
+++ a/src/include/catalog/pg_constraint.h	2019-02-28 06:30:00.617078441 +0000
@@ -224,7 +224,8 @@
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
-					  bool is_internal);
+					  bool is_internal,
+					  Oid existing_constraint_oid);
 
 extern void RemoveConstraintById(Oid conId);
 extern void RenameConstraintById(Oid conId, const char *newname);
diff -ur b/src/include/nodes/parsenodes.h a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h	2019-02-28 06:04:33.497078441 +0000
+++ a/src/include/nodes/parsenodes.h	2019-02-28 06:15:44.117078441 +0000
@@ -2401,6 +2401,7 @@
 	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
+	bool		replace;		/* T => replace if already exists */
 	/* The remaining fields are only used for constraint triggers */
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
diff -ur b/src/test/regress/expected/triggers.out a/src/test/regress/expected/triggers.out
--- b/src/test/regress/expected/triggers.out	2019-02-28 06:04:33.782078441 +0000
+++ a/src/test/regress/expected/triggers.out	2019-02-28 06:16:43.344078441 +0000
@@ -1715,6 +1715,394 @@
 drop table self_ref_trigger;
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
+create table my_table1 (id integer, name text);
+create table my_table2 (id integer);
+create function my_proc1() returns trigger as $$
+begin
+	return null;
+end;$$ language plpgsql;
+create function my_updateproc1() returns trigger as $$
+begin
+	        update my_table2 set id=new.id where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_deleteproc1() returns trigger as $$
+begin
+	        delete from my_table2 where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_insertproc1() returns trigger as $$
+begin
+	        insert into my_table2 values(new.id);
+		        return null;
+end;$$ language plpgsql;
+insert into my_table1 values(323, 'Alex');
+insert into my_table1 values(23, 'Teddy');
+insert into my_table1 values(38, 'Bob');
+insert into my_table2 values(323);
+insert into my_table2 values(23);
+insert into my_table2 values(38);
+-- Create regular trigger
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New trigger with the name my_regular_trigger created.
+delete from my_table1 where id=323;
+select * from my_table1;
+ id | name  
+----+-------
+ 23 | Teddy
+ 38 | Bob
+(2 rows)
+
+select * from my_table2;
+ id 
+----
+ 23
+ 38
+(2 rows)
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New constraint trigger with the name my_constraint_trigger created.
+delete from my_table1 where id=23;
+select * from my_table1;
+ id | name 
+----+------
+ 38 | Bob
+(1 row)
+
+select * from my_table2;
+ id 
+----
+ 38
+(1 row)
+
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT.
+insert into my_table1 values(323, 'Alex');
+select * from my_table1;
+ id  | name 
+-----+------
+  38 | Bob
+ 323 | Alex
+(2 rows)
+
+select * from my_table2;
+ id  
+-----
+  38
+ 323
+(2 rows)
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1
+DEFERRABLE INITIALLY DEFERRED
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT.
+insert into my_table1 values(23, 'Teddy');
+select * from my_table1;
+ id  | name  
+-----+-------
+  38 | Bob
+ 323 | Alex
+  23 | Teddy
+(3 rows)
+
+select * from my_table2;
+ id  
+-----
+  38
+ 323
+  23
+  23
+(4 rows)
+
+CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_regular_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  Trigger "my_regular_trigger" for relation "my_table1" cannot be replaced with constraint trigger
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown.
+CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_constraint_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  Constraint trigger "my_constraint_trigger" for relation "my_table1" cannot be replaced with non-constraint trigger
+--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown.
+CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_regular_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_constraint_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_updateproc1();
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+ALTER TABLE my_table1 DROP COLUMN name;
+DROP FUNCTION my_updateproc1();
+\d my_table1;
+             Table "public.my_table1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           |          | 
+Triggers:
+    my_constraint_trigger AFTER INSERT ON my_table1 DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION my_insertproc1()
+    my_regular_trigger AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE FUNCTION my_insertproc1()
+    my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1()
+
+CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+\h CREATE TRIGGER;
+Command:     CREATE TRIGGER
+Description: define a new trigger
+Syntax:
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
+    ON table_name
+    [ FROM referenced_table_name ]
+    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
+    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
+    [ FOR [ EACH ] { ROW | STATEMENT } ]
+    [ WHEN ( condition ) ]
+    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )
+
+where event can be one of:
+
+    INSERT
+    UPDATE [ OF column_name [, ... ] ]
+    DELETE
+    TRUNCATE
+
+SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2';
+                                          pg_get_triggerdef                                          
+-----------------------------------------------------------------------------------------------------
+ CREATE TRIGGER my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1()
+(1 row)
+
+----Clean up begin --------
+drop table my_table1;
+drop table my_table2;
+drop function my_deleteproc1();
+drop function my_insertproc1();
+drop function my_proc1();
+------Clean up end ---------
+------------------------------TESTCASE1-6 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various before triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST1 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST2 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST3 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+ --testcase1
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST1 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase2
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST2 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase3
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST3 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE7-12 AFTER TRIGGER---------------------------------------------------------
+---Check REPLACE against various after triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST7 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST8 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST9 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+ --testcase7
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST7 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase8
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST8 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase9
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST9 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE13-18 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various INSTEAD triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST13 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST14 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST15 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+--testcase13
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST13 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+--testcase14
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST14 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+--testcase15
+DROP TRIGGER sometrig  on my_table1;
+ERROR:  trigger "sometrig" for table "my_table1" does not exist
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST15 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+----Clean up begin --------
+drop view my_view1;
+drop table my_table1;
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+------Clean up end ---------
 --
 -- Check that statement triggers work correctly even with all children excluded
 --
diff -ur b/src/test/regress/sql/triggers.sql a/src/test/regress/sql/triggers.sql
--- b/src/test/regress/sql/triggers.sql	2019-02-28 06:04:33.825078441 +0000
+++ a/src/test/regress/sql/triggers.sql	2019-02-28 06:18:56.615078441 +0000
@@ -1177,6 +1177,313 @@
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
 
+create table my_table1 (id integer, name text);
+create table my_table2 (id integer);
+create function my_proc1() returns trigger as $$
+begin
+	return null;
+end;$$ language plpgsql;
+
+create function my_updateproc1() returns trigger as $$
+begin
+	        update my_table2 set id=new.id where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_deleteproc1() returns trigger as $$
+begin
+	        delete from my_table2 where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+
+create function my_insertproc1() returns trigger as $$
+begin
+	        insert into my_table2 values(new.id);
+		        return null;
+end;$$ language plpgsql;
+
+insert into my_table1 values(323, 'Alex');
+insert into my_table1 values(23, 'Teddy');
+insert into my_table1 values(38, 'Bob');
+insert into my_table2 values(323);
+insert into my_table2 values(23);
+insert into my_table2 values(38);
+-- Create regular trigger
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New trigger with the name my_regular_trigger created.
+
+delete from my_table1 where id=323;
+select * from my_table1;
+select * from my_table2;
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New constraint trigger with the name my_constraint_trigger created.
+
+delete from my_table1 where id=23;
+select * from my_table1;
+select * from my_table2;
+
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT.
+
+insert into my_table1 values(323, 'Alex');
+select * from my_table1;
+select * from my_table2;
+
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1
+DEFERRABLE INITIALLY DEFERRED
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT.
+
+insert into my_table1 values(23, 'Teddy');
+select * from my_table1;
+select * from my_table2;
+
+CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown.
+
+CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+
+CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown.
+
+CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+
+CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_updateproc1();
+
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+
+ALTER TABLE my_table1 DROP COLUMN name;
+DROP FUNCTION my_updateproc1();
+\d my_table1;
+
+CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+
+CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+
+\h CREATE TRIGGER;
+SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2';
+----Clean up begin --------
+
+drop table my_table1;
+drop table my_table2;
+drop function my_deleteproc1();
+drop function my_insertproc1();
+drop function my_proc1();
+------Clean up end ---------
+
+------------------------------TESTCASE1-6 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various before triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST1 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST2 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST3 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+ --testcase1
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase2
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase3
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE7-12 AFTER TRIGGER---------------------------------------------------------
+---Check REPLACE against various after triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST7 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST8 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST9 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+ --testcase7
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase8
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase9
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+
+
+------------------------------TESTCASE13-18 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various INSTEAD triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST13 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST14 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST15 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+--testcase13
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+--testcase14
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+--testcase15
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+----Clean up begin --------
+drop view my_view1;
+drop table my_table1;
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+------Clean up end ---------
 --
 -- Check that statement triggers work correctly even with all children excluded
 --
#2David Rowley
david.rowley@2ndquadrant.com
In reply to: Osumi, Takamichi (#1)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Thu, 28 Feb 2019 at 21:44, Osumi, Takamichi
<osumi.takamichi@jp.fujitsu.com> wrote:

I've made a patch to add CREATE OR REPLACE TRIGGER with some basic tests in triggers.sql.

Hi,

I see there are two patch entries in the commitfest for this. Is that
a mistake? If so can you "Withdraw" one of them?

--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#3Osumi, Takamichi
osumi.takamichi@jp.fujitsu.com
In reply to: David Rowley (#2)
RE: extension patch of CREATE OR REPLACE TRIGGER

I've made a patch to add CREATE OR REPLACE TRIGGER with some basic tests in triggers.sql.

I see there are two patch entries in the commitfest for this. Is that a
mistake? If so can you "Withdraw" one of them?

Oh my bad. Sorry, this time was my first time to register my patch !
Please withdraw the old one, "extension patch to add OR REPLACE clause to CREATE TRIGGER".
My latest version is "extension patch of CREATE OR REPLACE TRIGGER".

Thanks !
--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#4David Steele
david@pgmasters.net
In reply to: Osumi, Takamichi (#1)
Re: extension patch of CREATE OR REPLACE TRIGGER

On 2/28/19 10:43 AM, Osumi, Takamichi wrote:

One past thread about introducing CREATE OR REPLACE TRIGGER into the syntax

had stopped without complete discussion in terms of LOCK level.

The past thread is this. I'd like to inherit this one.

Since this patch landed at the last moment in the last commitfest for
PG12 I have marked it as targeting PG13.

Regards,
--
-David
david@pgmasters.net

#5Thomas Munro
thomas.munro@gmail.com
In reply to: David Steele (#4)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Tue, Mar 5, 2019 at 10:19 PM David Steele <david@pgmasters.net> wrote:

On 2/28/19 10:43 AM, Osumi, Takamichi wrote:

One past thread about introducing CREATE OR REPLACE TRIGGER into the syntax

had stopped without complete discussion in terms of LOCK level.

The past thread is this. I'd like to inherit this one.

Since this patch landed at the last moment in the last commitfest for
PG12 I have marked it as targeting PG13.

Hello Osumi-san,

The July Commitfest is now beginning. To give the patch the best
chance of attracting reviewers, could you please post a rebased
version? The last version doesn't apply.

Thanks,

--
Thomas Munro
https://enterprisedb.com

#6osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Thomas Munro (#5)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Mr. Thomas

The July Commitfest is now beginning. To give the patch the best chance of
attracting reviewers, could you please post a rebased version? The last version
doesn't apply.

I really appreciate your comments.
Recently, I'm very busy because of my work.
So, I'll fix this within a couple of weeks.

Regards,
Osumi Takamichi

#7Michael Paquier
michael@paquier.xyz
In reply to: osumi.takamichi@fujitsu.com (#6)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Wed, Jul 03, 2019 at 04:37:00AM +0000, osumi.takamichi@fujitsu.com wrote:

I really appreciate your comments.
Recently, I'm very busy because of my work.
So, I'll fix this within a couple of weeks.

Please note that I have switched the patch as waiting on author.
--
Michael

#8osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Michael Paquier (#7)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Michael san

So, I'll fix this within a couple of weeks.

Please note that I have switched the patch as waiting on author.

Thanks for your support.
I've rebased the previous patch to be applied
to the latest PostgreSQL without any failure of regression tests.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v02.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v02.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 3339a4b..65aeea3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 3b8c8b1..45bd58c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2400,7 +2400,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
-							  is_internal); /* internally constructed? */
+							  is_internal, /* internally constructed? */
+							  InvalidOid);							  
 
 	pfree(ccbin);
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bb60b23..ad5ec0e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1770,7 +1770,8 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
-								   is_internal);
+								   is_internal,
+								   InvalidOid);
 
 	/*
 	 * Register the index as internally dependent on the constraint.
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b614559..067e129 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,10 +76,11 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
-					  bool is_internal)
+					  bool is_internal,
+					  Oid existing_constraint_oid)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -92,7 +93,11 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc conscan;
+	ScanKeyData skey[2];
+	HeapTuple	tuple;
+	bool		replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -164,9 +169,11 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if(!existing_constraint_oid){
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -220,9 +227,44 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
-
-	CatalogTupleInsert(conDesc, tup);
+	if (OidIsValid(existing_constraint_oid)){
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}else{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3aee2d8..9bfdfab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8083,7 +8083,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false, /* is_internal */
+									  InvalidOid);
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -8353,7 +8354,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
-									  false);
+									  false,
+									  InvalidOid);
 
 			/*
 			 * Give this constraint partition-type dependencies on the parent
@@ -8750,7 +8752,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
-								  true);
+								  true,
+								  InvalidOid);
 
 		/* Set up partition dependencies for the new constraint */
 		ObjectAddressSet(address, ConstraintRelationId, constrOid);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 316692b..5025156 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -181,7 +182,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	HeapTuple	tuple;
 	Oid			fargtypes[1];	/* dummy */
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -191,6 +192,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
 
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
 	else
@@ -687,6 +695,59 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
+	 * Generate the trigger's OID now, so that we can use it in the name if
+	 * needed.
+	 */
+	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+								NULL, 1, &key);
+	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+	{
+		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+		if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+		{
+			// trigoid = HeapTupleGetOid(tuple); // raw code
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			trigger_exists = true;
+			break;
+		}
+	}
+	systable_endscan(tgscan);
+
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	
+	/* Replacement between normal triggers and constraint triggers are restricted */
+	if (stmt->replace && trigger_exists)
+	{
+		if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+		else if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+	}
+	else if (trigger_exists && !isInternal)
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DUPLICATE_OBJECT),
+			errmsg("trigger \"%s\" for relation \"%s\" already exists",
+			stmt->trigname, RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Find and validate the trigger function.
 	 */
 	if (!OidIsValid(funcoid))
@@ -776,19 +837,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* isnoinherit */
-											  isInternal);	/* is_internal */
+											  isInternal, /* is_internal */
+											  existing_constraint_oid);
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -806,37 +859,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -963,12 +985,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	heap_freetuple(tuple);
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+	
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -1011,6 +1072,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e9c8873..f4b414a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,7 +3174,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
-							  false);	/* is_internal */
+							  false,	/* is_internal */
+							  InvalidOid);
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 208b4a1..b9e9543 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5322,48 +5322,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7..f0f00b5 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -211,7 +211,9 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
-								  bool is_internal);
+								  bool is_internal,
+								  Oid existing_constraint_oid);
+								  
 
 extern void RemoveConstraintById(Oid conId);
 extern void RenameConstraintById(Oid conId, const char *newname);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c..e2da9f6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2418,6 +2418,7 @@ typedef struct CreateTrigStmt
 	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
+	bool		replace;		/* T => replace if already exists */
 	/* The remaining fields are only used for constraint triggers */
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index cd2b550..17125be 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -1715,6 +1715,396 @@ select * from self_ref_trigger;
 drop table self_ref_trigger;
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
+create table my_table1 (id integer, name text);
+create table my_table2 (id integer);
+create function my_proc1() returns trigger as $$
+begin
+	return null;
+end;$$ language plpgsql;
+create function my_updateproc1() returns trigger as $$
+begin
+	        update my_table2 set id=new.id where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_deleteproc1() returns trigger as $$
+begin
+	        delete from my_table2 where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_insertproc1() returns trigger as $$
+begin
+	        insert into my_table2 values(new.id);
+		        return null;
+end;$$ language plpgsql;
+insert into my_table1 values(323, 'Alex');
+insert into my_table1 values(23, 'Teddy');
+insert into my_table1 values(38, 'Bob');
+insert into my_table2 values(323);
+insert into my_table2 values(23);
+insert into my_table2 values(38);
+-- Create regular trigger
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New trigger with the name my_regular_trigger created.
+delete from my_table1 where id=323;
+select * from my_table1;
+ id | name  
+----+-------
+ 23 | Teddy
+ 38 | Bob
+(2 rows)
+
+select * from my_table2;
+ id 
+----
+ 23
+ 38
+(2 rows)
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New constraint trigger with the name my_constraint_trigger created.
+delete from my_table1 where id=23;
+select * from my_table1;
+ id | name 
+----+------
+ 38 | Bob
+(1 row)
+
+select * from my_table2;
+ id 
+----
+ 38
+(1 row)
+
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT.
+insert into my_table1 values(323, 'Alex');
+select * from my_table1;
+ id  | name 
+-----+------
+  38 | Bob
+ 323 | Alex
+(2 rows)
+
+select * from my_table2;
+ id  
+-----
+  38
+ 323
+(2 rows)
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1
+DEFERRABLE INITIALLY DEFERRED
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT.
+insert into my_table1 values(23, 'Teddy');
+select * from my_table1;
+ id  | name  
+-----+-------
+  38 | Bob
+ 323 | Alex
+  23 | Teddy
+(3 rows)
+
+select * from my_table2;
+ id  
+-----
+  38
+ 323
+  23
+  23
+(4 rows)
+
+CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_regular_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  Trigger "my_regular_trigger" for relation "my_table1" cannot be replaced with constraint trigger
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown.
+CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_constraint_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  Constraint trigger "my_constraint_trigger" for relation "my_table1" cannot be replaced with non-constraint trigger
+--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown.
+CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_regular_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+ERROR:  trigger "my_constraint_trigger" for relation "my_table1" already exists
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_updateproc1();
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+ALTER TABLE my_table1 DROP COLUMN name;
+DROP FUNCTION my_updateproc1();
+\d my_table1;
+             Table "public.my_table1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           |          | 
+Triggers:
+    my_constraint_trigger AFTER INSERT ON my_table1 DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION my_insertproc1()
+    my_regular_trigger AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE FUNCTION my_insertproc1()
+    my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1()
+
+CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+\h CREATE TRIGGER;
+Command:     CREATE TRIGGER
+Description: define a new trigger
+Syntax:
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
+    ON table_name
+    [ FROM referenced_table_name ]
+    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
+    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
+    [ FOR [ EACH ] { ROW | STATEMENT } ]
+    [ WHEN ( condition ) ]
+    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )
+
+where event can be one of:
+
+    INSERT
+    UPDATE [ OF column_name [, ... ] ]
+    DELETE
+    TRUNCATE
+
+URL: https://www.postgresql.org/docs/devel/sql-createtrigger.html
+
+SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2';
+                                          pg_get_triggerdef                                          
+-----------------------------------------------------------------------------------------------------
+ CREATE TRIGGER my_trigger2 AFTER DELETE ON my_table1 FOR EACH STATEMENT EXECUTE FUNCTION my_proc1()
+(1 row)
+
+----Clean up begin --------
+drop table my_table1;
+drop table my_table2;
+drop function my_deleteproc1();
+drop function my_insertproc1();
+drop function my_proc1();
+------Clean up end ---------
+------------------------------TESTCASE1-6 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various before triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST1 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST2 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST3 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+ --testcase1
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST1 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase2
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST2 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase3
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST3 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      7 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE7-12 AFTER TRIGGER---------------------------------------------------------
+---Check REPLACE against various after triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST7 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST8 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST9 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+ --testcase7
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST7 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase8
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST8 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+ --testcase9
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_table1 (id) values (1);
+NOTICE:  TEST9 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |      5 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE13-18 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various INSTEAD triggers
+CREATE TABLE my_table1 (id integer);
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST13 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST14 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST15 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+--testcase13
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST13 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+--testcase14
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST14 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+--testcase15
+DROP TRIGGER sometrig  on my_table1;
+ERROR:  trigger "sometrig" for table "my_table1" does not exist
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+insert into my_view1 (id) values (1);
+NOTICE:  TEST15 Trigger
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+  tgname  | tgtype | tgenabled | tgisinternal | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual 
+----------+--------+-----------+--------------+--------------+----------------+---------+--------+--------+--------
+ sometrig |     69 | O         | f            | f            | f              |       0 |        | \x     | 
+(1 row)
+
+----Clean up begin --------
+drop view my_view1;
+drop table my_table1;
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+------Clean up end ---------
 --
 -- Check that statement triggers work correctly even with all children excluded
 --
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 8f833b7..c0fce2e 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -1177,6 +1177,313 @@ drop table self_ref_trigger;
 drop function self_ref_trigger_ins_func();
 drop function self_ref_trigger_del_func();
 
+create table my_table1 (id integer, name text);
+create table my_table2 (id integer);
+create function my_proc1() returns trigger as $$
+begin
+	return null;
+end;$$ language plpgsql;
+
+create function my_updateproc1() returns trigger as $$
+begin
+	        update my_table2 set id=new.id where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+create function my_deleteproc1() returns trigger as $$
+begin
+	        delete from my_table2 where id=old.id;
+		        return null;
+end;$$ language plpgsql;
+
+create function my_insertproc1() returns trigger as $$
+begin
+	        insert into my_table2 values(new.id);
+		        return null;
+end;$$ language plpgsql;
+
+insert into my_table1 values(323, 'Alex');
+insert into my_table1 values(23, 'Teddy');
+insert into my_table1 values(38, 'Bob');
+insert into my_table2 values(323);
+insert into my_table2 values(23);
+insert into my_table2 values(38);
+-- Create regular trigger
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New trigger with the name my_regular_trigger created.
+
+delete from my_table1 where id=323;
+select * from my_table1;
+select * from my_table2;
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_deleteproc1();
+--Expected Result: New constraint trigger with the name my_constraint_trigger created.
+
+delete from my_table1 where id=23;
+select * from my_table1;
+select * from my_table2;
+
+CREATE OR REPLACE TRIGGER my_regular_trigger AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_regular_trigger definition with new definition having event as INSERT.
+
+insert into my_table1 values(323, 'Alex');
+select * from my_table1;
+select * from my_table2;
+
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_constraint_trigger AFTER INSERT ON my_table1
+DEFERRABLE INITIALLY DEFERRED
+FOR EACH ROW
+EXECUTE PROCEDURE my_insertproc1();
+--Expected Result: Replaces my_constraint_trigger definition with new definition having event as INSERT.
+
+insert into my_table1 values(23, 'Teddy');
+select * from my_table1;
+select * from my_table2;
+
+CREATE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+
+CREATE OR REPLACE CONSTRAINT TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation "my_table1" cannot be replaced with constraint trigger." should be shown.
+
+CREATE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+
+CREATE OR REPLACE TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Constraint trigger 'my_constraint_trigger' for relation "my_table1" cannot be replaced with non-constraint trigger." should be shown.
+
+CREATE TRIGGER my_regular_trigger AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_regular_trigger' for relation 'my_table1' already exists" should be shown.
+
+CREATE CONSTRAINT TRIGGER my_constraint_trigger AFTER DELETE ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+--Expected Result: Error message "Trigger 'my_constraint_trigger' for relation 'my_table' already exists" should be shown.
+
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER UPDATE OF NAME ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_updateproc1();
+
+CREATE OR REPLACE TRIGGER my_trigger2 AFTER DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+
+ALTER TABLE my_table1 DROP COLUMN name;
+DROP FUNCTION my_updateproc1();
+\d my_table1;
+
+CREATE TRIGGER my_trigger1 BEFORE DELETE ON my_table1
+EXECUTE PROCEDURE my_proc1();
+
+CREATE CONSTRAINT TRIGGER my_constraint_trigger2 AFTER INSERT ON my_table1
+FOR EACH ROW
+EXECUTE PROCEDURE my_proc1();
+
+\h CREATE TRIGGER;
+SELECT pg_get_triggerdef(oid, true) FROM pg_trigger WHERE tgname = 'my_trigger2';
+----Clean up begin --------
+
+drop table my_table1;
+drop table my_table2;
+drop function my_deleteproc1();
+drop function my_insertproc1();
+drop function my_proc1();
+------Clean up end ---------
+
+------------------------------TESTCASE1-6 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various before triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST1 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST2 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST3 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+ --testcase1
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase2
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase3
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig BEFORE INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+------------------------------TESTCASE7-12 AFTER TRIGGER---------------------------------------------------------
+---Check REPLACE against various after triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST7 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST8 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST9 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+ --testcase7
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase8
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+ --testcase9
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig AFTER INSERT ON my_table1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_table1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+DROP TRIGGER sometrig on my_table1;
+----Clean up begin --------
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+drop view my_view1;
+drop table my_table1;
+------Clean up end ---------
+
+
+------------------------------TESTCASE13-18 BEFORE TRIGGER---------------------------------------------------------
+---Check REPLACE against various INSTEAD triggers
+
+CREATE TABLE my_table1 (id integer);
+
+CREATE VIEW my_view1 AS SELECT id from my_table1;
+
+create function firstproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST13 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function secondproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST14 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function thirdproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'TEST15 Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+create function someproc() returns trigger as $$
+begin
+	        RAISE NOTICE 'Some Trigger';
+	        RETURN null;
+end;$$ language plpgsql;
+
+--testcase13
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE firstproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+--testcase14
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE secondproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+--testcase15
+DROP TRIGGER sometrig  on my_table1;
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE someproc();
+CREATE OR REPLACE TRIGGER sometrig INSTEAD OF INSERT ON my_view1 FOR EACH ROW EXECUTE PROCEDURE thirdproc();
+
+insert into my_view1 (id) values (1);
+select tgname,tgtype,tgenabled,tgisinternal,tgdeferrable,tginitdeferred,tgnargs,tgattr,tgargs,tgqual from pg_trigger where  tgname='sometrig';
+
+----Clean up begin --------
+drop view my_view1;
+drop table my_table1;
+drop function firstproc();
+drop function secondproc();
+drop function thirdproc();
+drop function someproc();
+------Clean up end ---------
 --
 -- Check that statement triggers work correctly even with all children excluded
 --
#9Surafel Temesgen
surafel3000@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#8)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hi Takamichi Osumi,
On Tue, Jul 9, 2019

I've rebased the previous patch to be applied

I don't test your patch fully yet but here are same comment.
There are same white space issue like here
-  bool is_internal)
+  bool is_internal,
+  Oid existing_constraint_oid)
in a few place

+ // trigoid = HeapTupleGetOid(tuple); // raw code
please remove this line if you don't use it.

+ if(!existing_constraint_oid){
+ conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);
+ values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ }
incorrect bracing style here and its appear in a few other places too
and it seems to me that the change in regression test is
huge can you reduce it?

regards
Surafel

#10osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Surafel Temesgen (#9)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Surafel

Thank you for your check of my patch.
I’ve made the version 03 to
fix what you mentioned about my patch.

I corrected my wrong bracing styles and
also reduced the amount of my regression test.
Off course, I erased unnecessary
white spaces and the C++ style comment.

Regards,
Takamichi Osumi

I don't test your patch fully yet but here are same comment.
There are same white space issue like here
-  bool is_internal)
+  bool is_internal,
+  Oid existing_constraint_oid)
in a few place

+ // trigoid = HeapTupleGetOid(tuple); // raw code
please remove this line if you don't use it.

+ if(!existing_constraint_oid){
+ conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);
+ values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ }
incorrect bracing style here and its appear in a few other places too
and it seems to me that the change in regression test is
huge can you reduce it?

regards
Surafel

Attachments:

CREATE_OR_REPLACE_TRIGGER_v03.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v03.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 3339a4b..65aeea3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 032fab9..0bf65cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2399,7 +2399,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
-							  is_internal); /* internally constructed? */
+							  is_internal, /* internally constructed? */
+							  InvalidOid);
 
 	pfree(ccbin);
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9234e93..dbe4a70 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1770,7 +1770,8 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
-								   is_internal);
+								   is_internal,
+								   InvalidOid);
 
 	/*
 	 * Register the index as internally dependent on the constraint.
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b614559..4d742f5 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,10 +76,11 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
-					  bool is_internal)
+					  bool is_internal,
+					  Oid existing_constraint_oid)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -92,7 +93,12 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc   conscan;
+	ScanKeyData   skey[2];
+	HeapTuple     tuple;
+	bool          replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
+	
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -164,9 +170,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if(!existing_constraint_oid)
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -220,9 +229,47 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
 
-	CatalogTupleInsert(conDesc, tup);
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fc1c4df..e8faac5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8081,7 +8081,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false, /* is_internal */
+									  InvalidOid);
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -8351,7 +8352,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
-									  false);
+									  false,
+									  InvalidOid);
 
 			/*
 			 * Give this constraint partition-type dependencies on the parent
@@ -8748,7 +8750,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
-								  true);
+								  true,
+								  InvalidOid);
 
 		/* Set up partition dependencies for the new constraint */
 		ObjectAddressSet(address, ConstraintRelationId, constrOid);
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index ee878d7..5188a6d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -181,7 +182,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	HeapTuple	tuple;
 	Oid			fargtypes[1];	/* dummy */
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -190,6 +191,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -687,6 +694,58 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
+	 * Generate the trigger's OID now, so that we can use it in the name if
+	 * needed.
+	 */
+	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+			Anum_pg_trigger_tgrelid,
+			BTEqualStrategyNumber, F_OIDEQ,
+			ObjectIdGetDatum(RelationGetRelid(rel)));
+	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+								NULL, 1, &key);
+	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+	{
+		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+		if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+		{
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			trigger_exists = true;
+			break;
+		}
+	}
+	systable_endscan(tgscan);
+
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+								Anum_pg_trigger_oid);
+
+	/* Replacement between normal triggers and constraint triggers are restricted */
+	if (stmt->replace && trigger_exists)
+	{
+		if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+		else if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+				stmt->trigname, RelationGetRelationName(rel))));
+	}
+	else if (trigger_exists && !isInternal)
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_DUPLICATE_OBJECT),
+			errmsg("trigger \"%s\" for relation \"%s\" already exists",
+			stmt->trigname, RelationGetRelationName(rel))));
+	}
+
+	/*
 	 * Find and validate the trigger function.
 	 */
 	if (!OidIsValid(funcoid))
@@ -776,19 +835,11 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* isnoinherit */
-											  isInternal);	/* is_internal */
+											  isInternal, /* is_internal */
+											  existing_constraint_oid);
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -806,37 +857,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -963,12 +983,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -1011,6 +1070,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8..3be6e61 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,7 +3174,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
-							  false);	/* is_internal */
+							  false,	/* is_internal */
+							  InvalidOid);
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb36..d662b03 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5322,48 +5322,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7..e48a6ce 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -211,7 +211,8 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
-								  bool is_internal);
+								  bool is_internal,
+								  Oid existing_constraint_oid);
 
 extern void RemoveConstraintById(Oid conId);
 extern void RenameConstraintById(Oid conId, const char *newname);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c..e2da9f6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2418,6 +2418,7 @@ typedef struct CreateTrigStmt
 	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
+	bool		replace;		/* T => replace if already exists */
 	/* The remaining fields are only used for constraint triggers */
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c64151b..16eedf2 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2848,3 +2848,49 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+\h CREATE TRIGGER;
+Command:     CREATE TRIGGER
+Description: define a new trigger
+Syntax:
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
+    ON table_name
+    [ FROM referenced_table_name ]
+    [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
+    [ REFERENCING { { OLD | NEW } TABLE [ AS ] transition_relation_name } [ ... ] ]
+    [ FOR [ EACH ] { ROW | STATEMENT } ]
+    [ WHEN ( condition ) ]
+    EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )
+
+where event can be one of:
+
+    INSERT
+    UPDATE [ OF column_name [, ... ] ]
+    DELETE
+    TRUNCATE
+
+URL: https://www.postgresql.org/docs/devel/sql-createtrigger.html
+
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE:  function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE:  function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 4534dc9..607c420 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2174,3 +2174,31 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+\h CREATE TRIGGER;
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: osumi.takamichi@fujitsu.com (#10)
Re: extension patch of CREATE OR REPLACE TRIGGER

"osumi.takamichi@fujitsu.com" <osumi.takamichi@fujitsu.com> writes:

[ CREATE_OR_REPLACE_TRIGGER_v03.patch ]

I took a quick look through this just to see what was going on.
A few comments:

* Upthread you asked about changing the lock level to be
AccessExclusiveLock if the trigger already exists, but the patch doesn't
actually do that. Which is fine by me, because that sounds like a
perfectly bad idea. In the first place, nobody is going to expect that
OR REPLACE changes the lock level, and in the second place, you can't
actually tell whether the trigger exists until you already have some
lock on the table. I do not put any credit in the argument that it's
more important to lock out pg_dump against a concurrent REPLACE TRIGGER
than it is to lock out a concurrent CREATE TRIGGER, anyway. So I think
keeping it at ShareRowExclusiveLock is fine.

* I wouldn't recommend adding CreateConstraintEntry's new argument
at the end. IME, "add at the end" is almost always bad coding style;
the right thing is "add where it belongs, that is where you'd have
put it if you were writing the list from scratch". To the admittedly
imperfect extent that the order of CreateConstraintEntry's arguments
matches the column order of pg_constraint, there's a good argument
that the OID should be *first*. (Maybe, as long as we've gotta touch
all the callers anyway, we should fix the other random deviations
from the catalog's column order, too.)

* While you're at it, it wouldn't hurt to fix CreateConstraintEntry's
header comment, maybe like

- * The new constraint's OID is returned.
+ * The new constraint's OID is returned.  (This will be the same as
+ * "conOid" if that is specified as nonzero.)

* The new code added to CreateTrigger could stand a rethink, too.
For starters, this comment does not describe the code stanza
just below it, but something considerably further down:

 	/*
+	 * Generate the trigger's OID now, so that we can use it in the name if
+	 * needed.
+	 */

It's also quite confusing because if there is a pre-existing trigger,
we *don't* generate any new OID. I'd make that say "See if there is a
pre-existing trigger of the same name", and then comment the later OID
generation appropriately. Also, the code below the pg_trigger search
seems pretty confused and redundant:

+	if (!trigger_exists)
+		// do something
+	if (stmt->replace && trigger_exists)
+	{
+		if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			//  do something
+		else if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			// do something
+	}
+	else if (trigger_exists && !isInternal)
+	{
+		// do something
+	}

I'm not on board with the idea that testing trigger_exists three separate
times, in three randomly-different-looking ways, makes things more
readable. I'm also not excited about spending the time to scan pg_trigger
at all in the isInternal case, where you're going to ignore the result.
So I think this could use some refactoring.

Also, in the proposed tests:

+\h CREATE TRIGGER;

We do not test \h output in any existing regression test, and we're
not going to start doing so in this one. For one thing, the expected
URL would break every time we forked off a new release branch.
(There would surely be value in having more-than-no test coverage
of psql/help.c, but that's a matter for its own patch, which would
need some thought about how to cope with instability of the output.)

regards, tom lane

#12Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#11)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Tue, Jul 30, 2019 at 04:44:11PM -0400, Tom Lane wrote:

We do not test \h output in any existing regression test, and we're
not going to start doing so in this one. For one thing, the expected
URL would break every time we forked off a new release branch.
(There would surely be value in having more-than-no test coverage
of psql/help.c, but that's a matter for its own patch, which would
need some thought about how to cope with instability of the output.)

One way to get out of that could be some psql-level options to control
which parts of the help output is showing up. The recent addition of
the URL may bring more weight for doing something in this area.
--
Michael

#13Thomas Munro
thomas.munro@gmail.com
In reply to: Michael Paquier (#12)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Wed, Jul 31, 2019 at 1:33 PM Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Jul 30, 2019 at 04:44:11PM -0400, Tom Lane wrote:

We do not test \h output in any existing regression test, and we're
not going to start doing so in this one. For one thing, the expected
URL would break every time we forked off a new release branch.
(There would surely be value in having more-than-no test coverage
of psql/help.c, but that's a matter for its own patch, which would
need some thought about how to cope with instability of the output.)

One way to get out of that could be some psql-level options to control
which parts of the help output is showing up. The recent addition of
the URL may bring more weight for doing something in this area.

Hello again Osumi-san,

The end of CF1 is here. I've moved this patch to CF2 (September) in
the Commitfest app. Of course, everyone is free to continue
discussing the patch before then. When you have a new version, please
set the status to "Needs review".

--
Thomas Munro
https://enterprisedb.com

#14Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: osumi.takamichi@fujitsu.com (#10)
Re: extension patch of CREATE OR REPLACE TRIGGER

On 2019-Jul-22, osumi.takamichi@fujitsu.com wrote:

Dear Surafel

Thank you for your check of my patch.
I’ve made the version 03 to
fix what you mentioned about my patch.

I corrected my wrong bracing styles and
also reduced the amount of my regression test.
Off course, I erased unnecessary
white spaces and the C++ style comment.

A new version of this patch, handling Tom's comments, would be
appreciated. Besides, per CFbot this patch applies no longer.

Thanks

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#15osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Tom Lane (#11)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Tom Lane

Thank you so much for your comment.

* Upthread you asked about changing the lock level to be AccessExclusiveLock if
the trigger already exists, but the patch doesn't actually do that. Which is fine by
me, because that sounds like a perfectly bad idea.

Why I suggested a discussion
to make the lock level of C.O.R.T. stronger above comes from my concern.

I've worried about a case that
C.O.R.T. weak lock like ShareRowExclusiveLock allows
one session to replace other session's trigger for new trigger by COMMIT;
As a result, the session is made to use the new one unintentionally.

As you can see below, the previous trigger is replaced by Session2 after applying this patch.
This seems to conflict with user's expectation to data consistency between sessions or
to identify C.O.R.T with DROP TRIGGER (AcessExclusive) + CREATE TRIGGER in terms of lock level.

-- Preparation
create table my_table1 (id integer, name text);
create table my_table2 (id integer, name text);
CREATE OR REPLACE FUNCTION public.my_updateproc1() RETURNS trigger LANGUAGE plpgsql
AS $function$
begin
UPDATE my_table2 SET name = 'new ' WHERE id=OLD.id;
RETURN NULL;
end;$function$;

CREATE OR REPLACE FUNCTION public.my_updateproc2() RETURNS trigger LANGUAGE plpgsql
AS $function$
begin
UPDATE my_table2 SET name = 'replace' WHERE id=OLD.id;
RETURN NULL;
end;$function$;

CREATE OR REPLACE TRIGGER my_regular_trigger AFTER UPDATE
ON my_table1 FOR EACH ROW EXECUTE PROCEDURE my_updateproc1();

--Session 1---
BEGIN;
select * from my_table1; -- Cause AccessShareLock here by referring to my_table1;

--Session 2---
BEGIN;
CREATE OR REPLACE TRIGGER my_regular_trigger
AFTER UPDATE ON my_table1 FOR EACH ROW
EXECUTE PROCEDURE my_updateproc2();
COMMIT;

--Session 1---
select pg_get_triggerdef(oid, true) from pg_trigger where tgrelid = 'my_table1'::regclass AND tgname = 'my_regular_trigger';
------------------------------------------------------------------------------------------------------------
CREATE TRIGGER my_regular_trigger AFTER UPDATE ON my_table1 FOR EACH ROW EXECUTE FUNCTION my_updateproc2()
(1 row)

By the way, I've fixed other points of my previous patch.

* I wouldn't recommend adding CreateConstraintEntry's new argument at the end.

I changed the order of CreateConstraintEntry function and its header comment.

Besides that,

I'm not on board with the idea that testing trigger_exists three separate times, in
three randomly-different-looking ways, makes things more readable.

I did code refactoring of the redundant and confusing part.

We do not test \h output in any existing regression test

And off course, I deleted the \h test you mentioned above.

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v04.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v04.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 3339a4b..65aeea3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9..e742354 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2371,7 +2371,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 098732c..210950a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1849,7 +1849,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 56568b0..a4dea4c 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -45,10 +45,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -79,7 +81,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -92,7 +94,12 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc   conscan;
+	ScanKeyData   skey[2];
+	HeapTuple     tuple;
+	bool          replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
+	
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -164,9 +171,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!existing_constraint_oid)
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -220,9 +230,47 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
 
-	CatalogTupleInsert(conDesc, tup);
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8d25d14..9b66e40 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8118,7 +8118,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8146,7 +8147,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false); /* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -8386,7 +8387,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8781,7 +8783,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 7ba859d..21d4053 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -181,7 +182,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	HeapTuple	tuple;
 	Oid			fargtypes[1];	/* dummy */
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -190,6 +191,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -686,6 +693,61 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* See if there is a pre-existing trigger of the same name */
+	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+			Anum_pg_trigger_tgrelid,
+			BTEqualStrategyNumber, F_OIDEQ,
+			ObjectIdGetDatum(RelationGetRelid(rel)));
+	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+								NULL, 1, &key);
+	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+	{
+		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+		if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+		{
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			trigger_exists = true;
+			break;
+		}
+	}
+	systable_endscan(tgscan);
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same name.
+		 */
+		if (!stmt->replace && !isInternal)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+		 */
+		if (stmt->replace && stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+		 */
+		if (stmt->replace && !stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -748,7 +810,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -780,15 +843,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -806,37 +860,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -963,12 +986,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -1011,6 +1073,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8..a6e3619 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3146,7 +3146,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..6ccb685 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5351,48 +5351,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7..e1249bb 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -183,7 +183,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..e195778 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2418,6 +2418,7 @@ typedef struct CreateTrigStmt
 	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
+	bool		replace;		/* T => replace if already exists */
 	/* The remaining fields are only used for constraint triggers */
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 1e4053c..ff3a7b1 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2918,3 +2918,27 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE:  function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE:  function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index c21b6c1..6ab5cfb 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2209,3 +2209,30 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
#16Michael Paquier
michael@paquier.xyz
In reply to: Thomas Munro (#13)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Thu, Aug 01, 2019 at 09:49:53PM +1200, Thomas Munro wrote:

The end of CF1 is here. I've moved this patch to CF2 (September) in
the Commitfest app. Of course, everyone is free to continue
discussing the patch before then. When you have a new version, please
set the status to "Needs review".

The latest patch includes calls to heap_open(), causing its
compilation to fail. Could you please send a rebased version of the
patch? I have moved the entry to next CF, waiting on author.
--
Michael

#17osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Michael Paquier (#16)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Michael san

The latest patch includes calls to heap_open(), causing its compilation to fail.
Could you please send a rebased version of the patch? I have moved the entry to
next CF, waiting on author.

Thanks. I've fixed where you pointed out.
Also, I'm waiting for other kind of feedbacks from anyone.

Regards,
Osumi Takamichi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v05.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v05.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 3339a4b..65aeea3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9..e742354 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2371,7 +2371,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 67f637d..f247cbc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1855,7 +1855,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 56568b0..06a2ead 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -45,10 +45,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -79,7 +81,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -92,7 +94,12 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc   conscan;
+	ScanKeyData   skey[2];
+	HeapTuple     tuple;
+	bool          replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
+	
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -164,9 +171,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!existing_constraint_oid)
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -220,9 +230,47 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
 
-	CatalogTupleInsert(conDesc, tup);
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5440eb9..6890606 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8125,7 +8125,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8153,7 +8154,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false); /* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -8393,7 +8394,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8788,7 +8790,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 8d22d43..8ed3ff3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -180,7 +181,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -189,6 +190,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -685,6 +692,61 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* See if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&key,
+			Anum_pg_trigger_tgrelid,
+			BTEqualStrategyNumber, F_OIDEQ,
+			ObjectIdGetDatum(RelationGetRelid(rel)));
+	tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+								NULL, 1, &key);
+	while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+	{
+		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+		if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+		{
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			trigger_exists = true;
+			break;
+		}
+	}
+	systable_endscan(tgscan);
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same name.
+		 */
+		if (!stmt->replace && !isInternal)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+		 */
+		if (stmt->replace && stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+		 */
+		if (stmt->replace && !stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -747,7 +809,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -779,15 +842,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -805,37 +859,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -962,12 +985,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -1010,6 +1072,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8..a6e3619 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3146,7 +3146,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c508684..d0e8108 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5353,48 +5353,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index cbb5d9a..b6531e9 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cb..29349c7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2418,6 +2418,7 @@ typedef struct CreateTrigStmt
 	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
+	bool		replace;		/* T => replace if already exists */
 	/* The remaining fields are only used for constraint triggers */
 	bool		deferrable;		/* [NOT] DEFERRABLE */
 	bool		initdeferred;	/* INITIALLY {DEFERRED|IMMEDIATE} */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 1e4053c..ff3a7b1 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -2918,3 +2918,27 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE:  function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE:  function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index c21b6c1..6ab5cfb 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2209,3 +2209,30 @@ drop table self_ref;
 drop function dump_insert();
 drop function dump_update();
 drop function dump_delete();
+
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
#18David Steele
david@pgmasters.net
In reply to: osumi.takamichi@fujitsu.com (#17)
Re: extension patch of CREATE OR REPLACE TRIGGER

On 12/1/19 8:56 PM, osumi.takamichi@fujitsu.com wrote:

The latest patch includes calls to heap_open(), causing its compilation to fail.
Could you please send a rebased version of the patch? I have moved the entry to
next CF, waiting on author.

Thanks. I've fixed where you pointed out.

This patch no longer applies: http://cfbot.cputube.org/patch_27_2307.log

CF entry has been updated to Waiting on Author.

Also, I'm waiting for other kind of feedbacks from anyone.

Hopefully a re-based patch will help with that.

Regards,
--
-David
david@pgmasters.net

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: osumi.takamichi@fujitsu.com (#17)
Re: extension patch of CREATE OR REPLACE TRIGGER

"osumi.takamichi@fujitsu.com" <osumi.takamichi@fujitsu.com> writes:

Also, I'm waiting for other kind of feedbacks from anyone.

As David pointed out, this needs to be rebased, though it looks like
the conflict is pretty trivial.

A few other notes from a quick look:

* You missed updating equalfuncs.c/copyfuncs.c. Pretty much any change in
a Node struct will require touching backend/nodes/ functions, and in
general it's a good idea to grep for uses of the struct to see what else
might be affected.

* Did you use a dartboard while deciding where to add the new field
in struct CreateTrigger? Its placement certainly seems quite random.
Maybe we should put both "replace" and "isconstraint" up near the
front, to match up with the statement's syntax.

* The patch doesn't appear to have any defenses against being asked to
replace the definition of, say, a foreign key trigger. It might be
sufficient to refuse to replace an entry that has tgisinternal set,
though I'm not sure if that covers all cases that we'd want to disallow.

* Speaking of which, I think you broke the isInternal case by insisting
on doing a lookup first. isInternal should *not* do a lookup, period,
especially not with the name it's initially given which will not be the
final trigger name. A conflict on that name is irrelevant, and so is
the OID of any pre-existing trigger.

* I'm not entirely sure that this patch interacts gracefully with
the provisions for per-partition triggers, either. Is the change
correctly cascaded to per-partition triggers if there are any?
Do we disallow making a change on a child partition trigger rather
than its parent? (Checking tgisinternal is going to be bound up
in that, since it looks like somebody decided to set that for child
triggers. I'm inclined to think that that was a dumb idea; we
may need to break out a separate tgischild flag so that we can tell
what's what.)

* I'm a little bit concerned about the semantics of changing the
tgdeferrable/tginitdeferred properties of an existing trigger. If there
are trigger events pending, and the trigger is redefined in such a way
that those events should already have been fired, what then? This doesn't
apply in other sessions, because taking ShareRowExclusiveLock should be
enough to ensure that no other session has uncommitted updates pending
against the table. But it *does* apply in our own session, because
ShareRowExclusiveLock won't conflict against our own locks. One answer
would be to run CheckTableNotInUse() once we discover that we're modifying
an existing trigger. Or we could decide that it doesn't matter --- if you
do that and it breaks, tough. For comparison, I notice that there doesn't
seem to be any guard against dropping a trigger that has pending events
in our own session, though that doesn't work out too well:

regression=# create constraint trigger my_trig after insert on trig_table deferrable initially deferred for each row execute procedure before_replacement();
CREATE TRIGGER
regression=# begin;
BEGIN
regression=*# insert into trig_table default values;
INSERT 0 1
regression=*# drop trigger my_trig on trig_table;
DROP TRIGGER
regression=*# commit;
ERROR: relation 38489 has no triggers

But arguably that's a bug to be fixed, not desirable behavior to emulate.

* Not the fault of this patch exactly, but trigger.c seems to have an
annoyingly large number of copies of the code to look up a trigger by
name. I wonder if we could refactor that, say by extracting the guts of
get_trigger_oid() into an internal function that's passed an already-open
pg_trigger relation.

* Upthread you were complaining about ShareRowExclusiveLock not being a
strong enough lock, but I think that's nonsense, for the same reason that
it's a sufficient lock for plain CREATE TRIGGER: if we have that lock then
no other session can have pending trigger events of any sort on the
relation, nor can new ones get made before we commit. But there's no
reason to lock out SELECTs on the relation, since those don't interact
with triggers.

regards, tom lane

#20osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Tom Lane (#19)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Dear Tom Lane

Thanks for your so many fruitful comments !

I have fixed my patch again.
On the other hand, there're some questions left
that I'd like to discuss.

* You missed updating equalfuncs.c/copyfuncs.c. Pretty much any change in a
Node struct will require touching backend/nodes/ functions, and in general it's a
good idea to grep for uses of the struct to see what else might be affected.

Yeah, thanks.

* Did you use a dartboard while deciding where to add the new field in struct
CreateTrigger? Its placement certainly seems quite random.
Maybe we should put both "replace" and "isconstraint" up near the front, to match
up with the statement's syntax.

By following the statement's syntax of CREATE TRIGGER,
I've listed up where I should change and fixed their orders.

* Speaking of which, I think you broke the isInternal case by insisting on doing a
lookup first. isInternal should *not* do a lookup, period, especially not with the
name it's initially given which will not be the final trigger name. A conflict on that
name is irrelevant, and so is the OID of any pre-existing trigger.

Sorry for this.
I inserted codes to skip the first lookup for isInternal case.
As a result, when isInternal is set true, trigger_exists flag never becomes true.
Doing a lookup first is necessary to fetch information
for following codes such as existing_constraint_oid to run CreateConstraintEntry().

* I'm not entirely sure that this patch interacts gracefully with the provisions for
per-partition triggers, either. Is the change correctly cascaded to per-partition
triggers if there are any?

Yes.
Please check added 4 test cases to prove that replacement of trigger
cascades to partition's trigger when there are other triggers on the relation.

* The patch doesn't appear to have any defenses against being asked to
replace the definition of, say, a foreign key trigger. It might be
sufficient to refuse to replace an entry that has tgisinternal set,
though I'm not sure if that covers all cases that we'd want to disallow.

Do we disallow making a change on a child partition trigger rather than its parent?
(Checking tgisinternal is going to be bound up in that, since it looks like somebody
decided to set that for child triggers. I'm inclined to think that that was a dumb
idea; we may need to break out a separate tgischild flag so that we can tell what's
what.)

Does this mean I need to add a new catalog member named 'tgischild' in pg_trigger?
This change sounds really widely influential, which means touching other many files additionally.
Isn't there any other way to distinguish trigger on partition table
from internally generated trigger ?
Otherwise, I need to fix many codes to achieve
the protection of internally generated trigger from being replaced.

* I'm a little bit concerned about the semantics of changing the
tgdeferrable/tginitdeferred properties of an existing trigger. If there are trigger
events pending, and the trigger is redefined in such a way that those events
should already have been fired, what then?

OK. I need a discussion about this point.
There would be two ideas to define the behavior of this semantics change, I think.
The first idea is to throw an error that means
the *pending* trigger can't be replaced during the session.
The second one is just to replace the trigger and ignite the new trigger
at the end of the session when its tginitdeferred is set true.
For me, the first one sounds safer. Yet, I'd like to know other opinions.

regression=# create constraint trigger my_trig after insert on trig_table
deferrable initially deferred for each row execute procedure
before_replacement(); CREATE TRIGGER regression=# begin; BEGIN
regression=*# insert into trig_table default values; INSERT 0 1 regression=*#
drop trigger my_trig on trig_table; DROP TRIGGER regression=*# commit;
ERROR: relation 38489 has no triggers

I could reproduce this bug, using the current master without my patch.
So this is another issue.
I'm thinking that throwing an error when *pending* trigger is dropped
makes sense. Does everyone agree with it ?

* Not the fault of this patch exactly, but trigger.c seems to have an annoyingly
large number of copies of the code to look up a trigger by name. I wonder if we
could refactor that, say by extracting the guts of
get_trigger_oid() into an internal function that's passed an already-open
pg_trigger relation.

While waiting for other reviews and comments, I'm willing to give it a try.

* Upthread you were complaining about ShareRowExclusiveLock not being a
strong enough lock, but I think that's nonsense, for the same reason that it's a
sufficient lock for plain CREATE TRIGGER: if we have that lock then no other
session can have pending trigger events of any sort on the relation, nor can new
ones get made before we commit. But there's no reason to lock out SELECTs on
the relation, since those don't interact with triggers.

Probably I misunderstand the function's priority like execution of pg_dump
and SELECTs. I'll sort out the information about this.

Other commends and reviews are welcome.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v06.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v06.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index bde3a63..675e3b6 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 632c058..fefea5d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2404,7 +2404,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index bd7ec92..3c2a503 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1873,7 +1873,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1938,6 +1939,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1949,7 +1952,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be..dc801a7 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -91,7 +93,12 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
-
+	SysScanDesc   conscan;
+	ScanKeyData   skey[2];
+	HeapTuple     tuple;
+	bool          replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
+	
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	Assert(constraintName);
@@ -163,9 +170,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!existing_constraint_oid)
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -219,9 +229,47 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
 
-	CatalogTupleInsert(conDesc, tup);
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	conobject.classId = ConstraintRelationId;
 	conobject.objectId = conOid;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 037d457..3481ced 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8583,7 +8583,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8611,7 +8612,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false); /* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -8851,7 +8852,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9253,7 +9255,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10319,6 +10322,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10339,7 +10344,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10368,6 +10372,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10376,7 +10382,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10424,6 +10429,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10432,7 +10439,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index ed551ab..4fc446b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,12 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +675,64 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if(!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same name.
+		 */
+		if (!stmt->replace && !isInternal)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+		 */
+		if (stmt->replace && stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+		 */
+		if (stmt->replace && !stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +760,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +793,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +810,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +937,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1024,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d..5a44533 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3077,7 +3077,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1525c0d..f7e9f58 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4314,6 +4314,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4322,7 +4324,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f34189..f398597 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2019,6 +2019,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2027,7 +2029,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1219ac8..5aeffdc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5391,48 +5391,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 518abe4..821e02d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2427,6 +2427,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2438,7 +2440,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index e9da4ef..e9a9551 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3007,3 +3007,152 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE:  function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE:  function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+NOTICE:  table "parted_trig" does not exist, skipping
+drop trigger if exists my_trig on parted_trig;
+NOTICE:  relation "parted_trig" does not exist, skipping
+drop function if exists before_replacement;
+NOTICE:  function before_replacement() does not exist, skipping
+drop function if exists after_replacement;
+NOTICE:  function after_replacement() does not exist, skipping
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  after
+insert into parted_trig (a) values (2500);
+NOTICE:  after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  after
+insert into parted_trig (a) values (2500);
+NOTICE:  after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 80ffbb4..ceb27c6 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2274,3 +2274,133 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+-- 
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+drop trigger if exists my_trig on parted_trig;
+drop function if exists before_replacement;
+drop function if exists after_replacement;
+
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;
#21Wolfgang Walther
walther@technowledgy.de
In reply to: osumi.takamichi@fujitsu.com (#20)
Re: extension patch of CREATE OR REPLACE TRIGGER

osumi.takamichi@fujitsu.com:

* I'm a little bit concerned about the semantics of changing the
tgdeferrable/tginitdeferred properties of an existing trigger. If there are trigger
events pending, and the trigger is redefined in such a way that those events
should already have been fired, what then?

OK. I need a discussion about this point.
There would be two ideas to define the behavior of this semantics change, I think.
The first idea is to throw an error that means
the *pending* trigger can't be replaced during the session.
The second one is just to replace the trigger and ignite the new trigger
at the end of the session when its tginitdeferred is set true.
For me, the first one sounds safer. Yet, I'd like to know other opinions.

IMHO, constraint triggers should behave the same in that regard as other
constraints. I just checked:

BEGIN;
CREATE TABLE t1 (a int CONSTRAINT u UNIQUE DEFERRABLE INITIALLY DEFERRED);
INSERT INTO t1 VALUES (1),(1);
ALTER TABLE t1 ALTER CONSTRAINT u NOT DEFERRABLE;

will throw with:

ERROR: cannot ALTER TABLE "t1" because it has pending trigger events
SQL state: 55006

So if a trigger event is pending, CREATE OR REPLACE for that trigger
should throw. I think it should do in any case, not just when changing
deferrability. This makes it easier to reason about.

If the user has a pending trigger, they can still do SET CONSTRAINTS
trigger_name IMMEDIATE; to resolve that and then do CREATE OR REPLACE
TRIGGER, just like in the ALTER TABLE case.

regression=# create constraint trigger my_trig after insert on trig_table
deferrable initially deferred for each row execute procedure
before_replacement(); CREATE TRIGGER regression=# begin; BEGIN
regression=*# insert into trig_table default values; INSERT 0 1 regression=*#
drop trigger my_trig on trig_table; DROP TRIGGER regression=*# commit;
ERROR: relation 38489 has no triggers

I could reproduce this bug, using the current master without my patch.
So this is another issue.
I'm thinking that throwing an error when *pending* trigger is dropped
makes sense. Does everyone agree with it ?

Just tested the same example as above, but with DROP TABLE t1; instead
of ALTER TABLE. This throws with:

ERROR: cannot DROP TABLE "t1" because it has pending trigger events
SQL state: 55006

So yes, your suggestion makes a lot of sense!

#22Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#20)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Thu, Aug 20, 2020 at 12:11 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

I have fixed my patch again.

Hi Osumi-san,

FYI - The latest patch (v06) has conflicts when applied.

Kind Regards,
Peter Smith.
Fujitsu Australia

#23osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#22)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi,

Thanks, Peter.

FYI - The latest patch (v06) has conflicts when applied.

I've fixed my v06 and created v07.
Also, I added one test to throw an error
to avoid a situation that trigger which has pending events are replaced by
any other trigger in the same session.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v07.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v07.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 289dd1d..242b4d3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index f2ca686..893f4b0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2442,7 +2442,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1be27ee..e9a1c97 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1848,7 +1848,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1913,6 +1914,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1924,7 +1927,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 6a6b2cb..e750857 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -91,6 +93,11 @@ CreateConstraintEntry(const char *constraintName,
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
+	SysScanDesc   conscan;
+	ScanKeyData   skey[2];
+	HeapTuple     tuple;
+	bool          replaces[Natts_pg_constraint];
+	Form_pg_constraint constrForm;
 
 	conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
 
@@ -163,9 +170,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!existing_constraint_oid)
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -219,9 +229,47 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
+
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
 
-	CatalogTupleInsert(conDesc, tup);
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2b15a3..c2ed4ae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8788,7 +8788,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -8816,7 +8817,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false);	/* is_internal */
+									  false); /* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
 
@@ -9056,7 +9057,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9458,7 +9460,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10469,6 +10472,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10489,7 +10494,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10518,6 +10522,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10526,7 +10532,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10574,6 +10579,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10582,7 +10589,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..a920fc3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		trigger_deferrable = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +676,74 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if(!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				trigger_deferrable = pg_trigger->tgdeferrable;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * If this trigger has pending event, throw an error.
+		 */
+		if (stmt->replace && !isInternal && trigger_deferrable &&
+			AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_IN_USE),
+					 errmsg("cannot replace \"%s\" on \"%s\" because it has pending trigger events",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same name.
+		 */
+		if (!stmt->replace && !isInternal)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+		 */
+		if (stmt->replace && stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+		/*
+		 * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+		 */
+		if (stmt->replace && !stmt->isconstraint && OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be replaced with non-constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +771,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +804,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +821,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +948,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1035,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In case of replace trigger, trigger should no-more dependent on old
+	 * referenced objects. Always remove the old dependencies and then
+	 * register new ones. In that way, even if the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409d..56f0df4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c4..ec36ebd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4..0a89c3a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5214,48 +5214,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 47d4c07..c8958d1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2428,6 +2428,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2439,7 +2441,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..30dd696 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,177 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+NOTICE:  function replaced by another function
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+NOTICE:  function to replace the initial function
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+NOTICE:  table "parted_trig" does not exist, skipping
+drop trigger if exists my_trig on parted_trig;
+NOTICE:  relation "parted_trig" does not exist, skipping
+drop function if exists before_replacement;
+NOTICE:  function before_replacement() does not exist, skipping
+drop function if exists after_replacement;
+NOTICE:  function after_replacement() does not exist, skipping
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  after
+insert into parted_trig (a) values (2500);
+NOTICE:  after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  before
+insert into parted_trig (a) values (2500);
+NOTICE:  before
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+NOTICE:  after
+insert into parted_trig (a) values (2500);
+NOTICE:  after
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  before
+insert into parted_trig_1 (a) values (1);
+NOTICE:  before
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  before
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+NOTICE:  after
+insert into parted_trig_1 (a) values (1);
+NOTICE:  after
+insert into parted_trig_1_1 (a) values (10);
+NOTICE:  after
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function not_replaced () returns trigger as $$
+begin
+raise notice 'this function is not replaced by other function in this test';
+return null;
+end; $$ language plpgsql;
+create function fail_to_replace() returns trigger as $$
+begin
+raise notice 'cause failure of dropping a trigger with a pending event';
+return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig after insert on my_table
+       deferrable initially deferred
+       for each row execute procedure not_replaced();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig after insert on my_table
+       deferrable initially deferred
+       for each row execute procedure fail_to_replace(); -- should fail
+ERROR:  cannot replace "my_trig" on "my_table" because it has pending trigger events
+end;
+drop table my_table;
+drop function not_replaced;
+drop function fail_to_replace;
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..b279fbe 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,158 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure before_replacement();
+insert into my_table (id) values (1);
+
+create or replace trigger my_trig before insert on my_table for each row execute procedure after_replacement();
+insert into my_table (id) values (2);
+
+drop trigger my_trig on my_table;
+drop function before_replacement();
+drop function after_replacement();
+drop table my_table;
+
+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+drop trigger if exists my_trig on parted_trig;
+drop function if exists before_replacement;
+drop function if exists after_replacement;
+
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+
+create function before_replacement() returns trigger as $$
+begin
+raise notice 'before';
+return null;
+end; $$ language plpgsql;
+
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'after';
+return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- vefify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child parition is replaced and no other partition has the partition.
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure before_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+create or replace trigger my_trig after insert on parted_trig_1 for each row execute procedure after_replacement();
+insert into parted_trig (a) values (1);
+insert into parted_trig_1 (a) values (1);
+insert into parted_trig_1_1 (a) values (10);
+insert into parted_trig_2 (a) values (1500);
+insert into parted_trig (a) values (2500);
+
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function before_replacement;
+drop function after_replacement;
+
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function not_replaced () returns trigger as $$
+begin
+raise notice 'this function is not replaced by other function in this test';
+return null;
+end; $$ language plpgsql;
+create function fail_to_replace() returns trigger as $$
+begin
+raise notice 'cause failure of dropping a trigger with a pending event';
+return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig after insert on my_table
+       deferrable initially deferred
+       for each row execute procedure not_replaced();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig after insert on my_table
+       deferrable initially deferred
+       for each row execute procedure fail_to_replace(); -- should fail
+end;
+drop table my_table;
+drop function not_replaced;
+drop function fail_to_replace;
#24Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#23)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Mon, Aug 24, 2020 at 9:33 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

I've fixed my v06 and created v07.

Hi Osumi-san.

I have reviewed the source code of the v07 patch.

(I also reviewed the test cases but I will share those comments as a
separate post).

Below are my comments - sorry, many of them are very minor.

====

COMMENT pg_constraint.c (wrong comment?)

- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)

Shouldn't that comment say:
(This will be the same as "existing_constraint_oid" if that is other
than InvalidOid)

====

COMMENT pg_constraint.c (declarations)

@@ -91,6 +93,11 @@ CreateConstraintEntry(const char *constraintName,
  NameData cname;
  int i;
  ObjectAddress conobject;
+ SysScanDesc   conscan;
+ ScanKeyData   skey[2];
+ HeapTuple     tuple;
+ bool          replaces[Natts_pg_constraint];
+ Form_pg_constraint constrForm;

Maybe it is more convenient/readable to declare these in the scope
where they are actually used.

====

COMMENT pg_constraint.c (oid checking)

- conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
- Anum_pg_constraint_oid);
- values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ if (!existing_constraint_oid)
+ {
+ conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);

Maybe better to use if (!OidIsValid(existing_constraint_oid)) here.

====

COMMENT tablecmds.c (unrelated change)

-   false); /* is_internal */
+   false); /* is_internal */

Some whitespace which has nothing to do with the patch was changed.

====

COMMENT trigger.c (declarations / whitespace)

+ bool is_update = false;
+ HeapTuple newtup;
+ TupleDesc tupDesc;
+ bool replaces[Natts_pg_trigger];
+ Oid existing_constraint_oid = InvalidOid;
+ bool trigger_exists = false;
+ bool trigger_deferrable = false;

1. One of those variables is misaligned with tabbing.

2. Maybe it is more convenient/readable to declare some of these in
the scope where they are actually used.
e.g. newtup, tupDesc, replaces.

====

COMMENT trigger.c (error messages)

+ /*
+ * If this trigger has pending event, throw an error.
+ */
+ if (stmt->replace && !isInternal && trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("cannot replace \"%s\" on \"%s\" because it has pending
trigger events",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * without OR REPLACE clause, can't override the trigger with the same name.
+ */
+ if (!stmt->replace && !isInternal)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
non-constraint trigger.
+ */
+ if (stmt->replace && stmt->isconstraint &&
!OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with
constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+ */
+ if (stmt->replace && !stmt->isconstraint &&
OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be
replaced with non-constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));

1. The order of these new errors is confusing. Maybe do the "already
exists" check first so that all the REPLACE errors can be grouped
together.

2. There is inconsistent message text capitalising the 1st word.
Should all be lower [1]https://www.postgresql.org/docs/current/error-style-guide.html
[1]: https://www.postgresql.org/docs/current/error-style-guide.html

3. That "already exists" error might benefit from a message hint. e.g.
---
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists", ...),
errhint("use CREATE OR REPLACE to replace it")));
---

4. Those last two errors seem like a complicated way just to say the
trigger types are not compatible. Maybe these messages can be
simplified, and some message hints added. e.g.
---
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger", ...),
errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));

ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger", ...),
errhint("use CREATE OR REPLACE CONSTRAINT to replace a constraint
trigger")));
---

====

COMMENT trigger.c (comment wording)

+ * In case of replace trigger, trigger should no-more dependent on old
+ * referenced objects. Always remove the old dependencies and then

Needs re-wording.

====

Kind Regards,
Peter Smith.
Fujitsu Australia

#25Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#23)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Mon, Aug 24, 2020 at 9:33 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

I've fixed my v06 and created v07.

Hi Osumi-san.

I have reviewed the test code of the v07 patch.

Below are my comments.

====

COMMENT (confusing functions)

+create function before_replacement() returns trigger as $$
+begin
+raise notice 'function replaced by another function';
+return null;
+end; $$ language plpgsql;
+create function after_replacement() returns trigger as $$
+begin
+raise notice 'function to replace the initial function';
+return null;
+end; $$ language plpgsql;

Why have function names with a hard-wired dependency on how you expect
they will be called.
I think just call them "funcA" and "funcB" is much easier and works
just as well. e.g.
---
create function funcA() returns trigger as $$
begin
raise notice 'hello from funcA';
return null;
end; $$ language plpgsql;

create function funcB() returns trigger as $$
begin
raise notice 'hello from funcB';
return null;
end; $$ language plpgsql;
---

And this same comment applies for all the other test functions created
for this v07 patch.

====

COMMENT (drops)

+-- setup for another test of CREATE OR REPLACE TRIGGER
+drop table if exists parted_trig;
+NOTICE:  table "parted_trig" does not exist, skipping
+drop trigger if exists my_trig on parted_trig;
+NOTICE:  relation "parted_trig" does not exist, skipping
+drop function if exists before_replacement;
+NOTICE:  function before_replacement() does not exist, skipping
+drop function if exists after_replacement;
+NOTICE:  function after_replacement() does not exist, skipping

Was it deliberate to attempt to drop the trigger after dropping the table?
Also this seems to be dropping functions which were already dropped
just several lines earlier.

====

COMMENT (typos)

There are a couple of typos in the test comments. e.g.
"vefify" -> "verify"
"child parition" -> "child partition"

====

COMMENT (partition table inserts)

1. Was it deliberate to insert explicitly into each partition table?
Why not insert everything into the top table and let the partitions
take care of themselves?

2. The choice of values to insert also seemed strange. Inserting 1 and
1 and 10 is going to all end up in the "parted_trig_1_1".

To summarise, I thought all subsequent partition tests maybe should be
inserting more like this:
---
insert into parted_trig (a) values (50); -- into parted_trig_1_1
insert into parted_trig (a) values (1500); -- into parted_trig_2
insert into parted_trig (a) values (2500); -- into default_parted_trig
---

====

COMMENT (missing error test cases)

There should be some more test cases to cover the new error messages
that were added to trigger.c:

e.g. test for "can't create regular trigger because already exists"
e.g. test for "can't create constraint trigger because already exists"
e.g. test for "can't replace regular trigger with constraint trigger""
e.g. test for "can't replace constraint trigger with regular trigger"
etc.

====

COMMENT (trigger with pending events)

This is another test where the complexity of the functions
("not_replaced", and "fail_to_replace") seemed excessive.
I think just calling these "funcA" and "funcB" as mentioned above
would be easier, and would serve just as well.

====

Kind Regards,
Peter Smith.
Fujitsu Australia

#26osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#25)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi, Peter

You gave me two posts for my patch review.
Thank you so much. I'll write all my replies into this post.

====

COMMENT pg_constraint.c (wrong comment?)

- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "conOid" if that is specified as nonzero.)

Shouldn't that comment say:
(This will be the same as "existing_constraint_oid" if that is other than
InvalidOid)

Thanks. I corrected this part.

====

COMMENT pg_constraint.c (declarations)

@@ -91,6 +93,11 @@ CreateConstraintEntry(const char *constraintName,
NameData cname;
int i;
ObjectAddress conobject;
+ SysScanDesc   conscan;
+ ScanKeyData   skey[2];
+ HeapTuple     tuple;
+ bool          replaces[Natts_pg_constraint];
+ Form_pg_constraint constrForm;

Maybe it is more convenient/readable to declare these in the scope where they
are actually used.

You're right. Fixed.

====

COMMENT pg_constraint.c (oid checking)

- conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
- Anum_pg_constraint_oid);
- values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+ if (!existing_constraint_oid)
+ {
+ conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);

Maybe better to use if (!OidIsValid(existing_constraint_oid)) here.

Got it. I replaced that part by OidIsValid ().

====

COMMENT tablecmds.c (unrelated change)

-   false); /* is_internal */
+   false); /* is_internal */

Some whitespace which has nothing to do with the patch was changed.

Yeah. Fixed.

====

COMMENT trigger.c (declarations / whitespace)

+ bool is_update = false;
+ HeapTuple newtup;
+ TupleDesc tupDesc;
+ bool replaces[Natts_pg_trigger];
+ Oid existing_constraint_oid = InvalidOid; bool trigger_exists = false;
+ bool trigger_deferrable = false;

1. One of those variables is misaligned with tabbing.

Fixed.

2. Maybe it is more convenient/readable to declare some of these in the scope
where they are actually used.
e.g. newtup, tupDesc, replaces.

I cannot do this because those variables are used
at the top level in this function. Anyway, thanks for the comment.

====

COMMENT trigger.c (error messages)

+ /*
+ * If this trigger has pending event, throw an error.
+ */
+ if (stmt->replace && !isInternal && trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("cannot replace \"%s\" on \"%s\" because it has pending
trigger events",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * without OR REPLACE clause, can't override the trigger with the same name.
+ */
+ if (!stmt->replace && !isInternal)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
non-constraint trigger.
+ */
+ if (stmt->replace && stmt->isconstraint &&
!OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Trigger \"%s\" for relation \"%s\" cannot be replaced with
constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ /*
+ * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+ */
+ if (stmt->replace && !stmt->isconstraint &&
OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("Constraint trigger \"%s\" for relation \"%s\" cannot be
replaced with non-constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));

1. The order of these new errors is confusing. Maybe do the "already exists"
check first so that all the REPLACE errors can be grouped together.

OK. I sorted out the order and conditions for this part.

2. There is inconsistent message text capitalising the 1st word.
Should all be lower [1]
[1] - https://www.postgresql.org/docs/current/error-style-guide.html

Fixed.

3. That "already exists" error might benefit from a message hint. e.g.
---
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" already exists", ...), errhint("use
CREATE OR REPLACE to replace it")));
---

4. Those last two errors seem like a complicated way just to say the trigger
types are not compatible. Maybe these messages can be simplified, and some
message hints added. e.g.
---
ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger", ...),
errhint("use CREATE OR REPLACE TRIGGER to replace a regular
trigger")));

ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger", ...),
errhint("use CREATE OR REPLACE CONSTRAINT to replace a constraint
trigger")));

I simplified those messages.

====

COMMENT trigger.c (comment wording)

+ * In case of replace trigger, trigger should no-more dependent on old
+ * referenced objects. Always remove the old dependencies and then

Needs re-wording.

Fixed.

====

COMMENT (confusing functions)

+create function before_replacement() returns trigger as $$ begin raise
+notice 'function replaced by another function'; return null; end; $$
+language plpgsql; create function after_replacement() returns trigger
+as $$ begin raise notice 'function to replace the initial function';
+return null; end; $$ language plpgsql;

Why have function names with a hard-wired dependency on how you expect
they will be called.
I think just call them "funcA" and "funcB" is much easier and works just as well.
e.g.
---
create function funcA() returns trigger as $$ begin raise notice 'hello from
funcA'; return null; end; $$ language plpgsql;

create function funcB() returns trigger as $$ begin raise notice 'hello from
funcB'; return null; end; $$ language plpgsql;
---

Got it. I simplified such kind of confusing names. Thanks.

====

COMMENT (drops)

+-- setup for another test of CREATE OR REPLACE TRIGGER drop table if
+exists parted_trig;
+NOTICE:  table "parted_trig" does not exist, skipping drop trigger if
+exists my_trig on parted_trig;
+NOTICE:  relation "parted_trig" does not exist, skipping drop function
+if exists before_replacement;
+NOTICE:  function before_replacement() does not exist, skipping drop
+function if exists after_replacement;
+NOTICE:  function after_replacement() does not exist, skipping

Was it deliberate to attempt to drop the trigger after dropping the table?
Also this seems to be dropping functions which were already dropped just
several lines earlier.

This wasn't necessary. I deleted those.

====

COMMENT (typos)

There are a couple of typos in the test comments. e.g.
"vefify" -> "verify"
"child parition" -> "child partition"

Fixed.

====

COMMENT (partition table inserts)

1. Was it deliberate to insert explicitly into each partition table?
Why not insert everything into the top table and let the partitions take care of
themselves?

Actually, yes. I wanted readers of this code to easily identify which partitioned is used.
But, I fixed those because it was redundant and not smart. Your suggestion sounds better.

2. The choice of values to insert also seemed strange. Inserting 1 and
1 and 10 is going to all end up in the "parted_trig_1_1".

To summarise, I thought all subsequent partition tests maybe should be
inserting more like this:
---
insert into parted_trig (a) values (50); -- into parted_trig_1_1 insert into
parted_trig (a) values (1500); -- into parted_trig_2 insert into parted_trig (a)
values (2500); -- into default_parted_trig

This makes sense. I adopted your idea.

====

COMMENT (missing error test cases)

There should be some more test cases to cover the new error messages that
were added to trigger.c:

e.g. test for "can't create regular trigger because already exists"
e.g. test for "can't create constraint trigger because already exists"
e.g. test for "can't replace regular trigger with constraint trigger""
e.g. test for "can't replace constraint trigger with regular trigger"
etc.

I've added those tests additionally.

====

COMMENT (trigger with pending events)

This is another test where the complexity of the functions ("not_replaced", and
"fail_to_replace") seemed excessive.
I think just calling these "funcA" and "funcB" as mentioned above would be
easier, and would serve just as well.

I modified the names of functions.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v08.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v08.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 289dd1d..242b4d3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index f2ca686..893f4b0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2442,7 +2442,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 1be27ee..e9a1c97 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1848,7 +1848,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1913,6 +1914,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1924,7 +1927,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 6a6b2cb..6f0fd9d 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -163,9 +165,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -219,9 +224,56 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc   conscan;
+		ScanKeyData   skey[2];
+		HeapTuple     tuple;
+		bool          replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+						 ConstraintOidIndexId,
+						 true,
+						 NULL,
+						 1,
+						 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert (HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false; /* skip updating Oid data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d2b15a3..bfa78bb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8788,7 +8788,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9056,7 +9057,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9458,7 +9460,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10469,6 +10472,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10489,7 +10494,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10518,6 +10522,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10526,7 +10532,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10574,6 +10579,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10582,7 +10589,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..c57c062 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	HeapTuple	newtup;
+	TupleDesc	tupDesc;
+	bool		replaces[Natts_pg_trigger];
+	Oid		existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		trigger_deferrable = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +676,77 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				trigger_deferrable = pg_trigger->tgdeferrable;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else{
+			/*
+			 * If this trigger has pending events, throw an error.
+			 */
+			if (trigger_deferrable && AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_IN_USE),
+						 errmsg("cannot replace \"%s\" on \"%s\" because it has pending trigger events",
+								stmt->trigname, RelationGetRelationName(rel))));
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a costraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +774,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +807,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +824,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +951,51 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+				Anum_pg_trigger_tgrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false; /* skip updating Oid data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1038,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then
+	 * register new ones. In this case, while the old referenced object gets
+	 * dropped, trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409d..56f0df4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c4..ec36ebd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4..0a89c3a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5214,48 +5214,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 47d4c07..c8958d1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2428,6 +2428,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2439,7 +2441,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..914dcb7 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,222 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting imcompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a costraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+ERROR:  cannot replace "my_trig" on "my_table" because it has pending trigger events
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..cb18308 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,204 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting imcompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
#27Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#26)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hi Osumi-san.

Thanks for addressing my previous review comments in your new v08 patch.

I have checked it again.

My only remaining comments are for trivial stuff:

====

COMMENT trigger.c (tab alignment)

@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
  char    *oldtablename = NULL;
  char    *newtablename = NULL;
  bool partition_recurse;
+ bool is_update = false;
+ HeapTuple newtup;
+ TupleDesc tupDesc;
+ bool replaces[Natts_pg_trigger];
+ Oid existing_constraint_oid = InvalidOid;
+ bool trigger_exists = false;
+ bool trigger_deferrable = false;

Maybe my editor configuration is wrong, but the alignment of
"existing_constraint_oid" still does not look fixed to me.

====

COMMENT trigger.c (move some declarations)

2. Maybe it is more convenient/readable to declare some of these in the scope
where they are actually used.
e.g. newtup, tupDesc, replaces.

I cannot do this because those variables are used
at the top level in this function. Anyway, thanks for the comment.

Are you sure it can't be done? It looks doable to me.

====

COMMENT trigger.c ("already exists" message)

In my v07 review I suggested adding a hint for the new "already exists" error.
Of course choice is yours, but since you did not add it I just wanted
to ask was that accidental or deliberate?

====

COMMENT triggers.sql/.out (typo in comment)

+-- test for detecting imcompatible replacement of trigger

"imcompatible" -> "incompatible"

====

COMMENT triggers.sql/.out (wrong comment)

+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a costraint trigger

I think the first "--should fail" comment is misleading. First time is OK.

====

Kind Regards,
Peter Smith.
Fujitsu Australia

#28osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#27)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi, Peter-San

I've fixed all except one point.

My only remaining comments are for trivial stuff:

Not trivial but important.

COMMENT trigger.c (tab alignment)

@@ -184,6 +185,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
char    *oldtablename = NULL;
char    *newtablename = NULL;
bool partition_recurse;
+ bool is_update = false;
+ HeapTuple newtup;
+ TupleDesc tupDesc;
+ bool replaces[Natts_pg_trigger];
+ Oid existing_constraint_oid = InvalidOid; bool trigger_exists = false;
+ bool trigger_deferrable = false;

Maybe my editor configuration is wrong, but the alignment of
"existing_constraint_oid" still does not look fixed to me.

You were right. In order to solve this point completely,
I've executed pgindent and gotten clean alignment.
How about v09 ?
Other alignments of C source codes have been fixed as well.
This is mainly comments, though.

COMMENT trigger.c (move some declarations)

2. Maybe it is more convenient/readable to declare some of these in
the scope where they are actually used.
e.g. newtup, tupDesc, replaces.

I cannot do this because those variables are used at the top level in
this function. Anyway, thanks for the comment.

Are you sure it can't be done? It looks doable to me.

Done. I was wrong. Thank you.

COMMENT trigger.c ("already exists" message)

In my v07 review I suggested adding a hint for the new "already exists" error.
Of course choice is yours, but since you did not add it I just wanted to ask was
that accidental or deliberate?

This was deliberate.
The code path of "already exists" error you mentioned above
is used for other errors as well. For example, a failure case of
"ALTER TABLE name ATTACH PARTITION partition_name...".

This command fails if the "partition_name" table has a trigger,
whose name is exactly same as the trigger on the "name" table.
For each user-defined row-level trigger that exists in the "name" table,
a corresponding one is created in the attached table, automatically.
Thus, the "ALTER TABLE" command throws the error which says
trigger "name" for relation "partition_name" already exists.
I felt if I add the hint, application developer would get confused.
Did it make sense ?

COMMENT triggers.sql/.out (typo in comment)

+-- test for detecting imcompatible replacement of trigger

"imcompatible" -> "incompatible"

Fixed.

COMMENT triggers.sql/.out (wrong comment)

+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail create or
+replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint
+trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a
costraint
+trigger

I think the first "--should fail" comment is misleading. First time is OK.

Thanks. Removed the misleading comment.

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v09.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v09.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 289dd1d..242b4d3 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..1a52fa3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2434,7 +2434,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b2c8cb3..c3f5747 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1861,7 +1861,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1926,6 +1927,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1937,7 +1940,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0..3f35e50 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -165,9 +167,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -221,9 +226,57 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc conscan;
+		ScanKeyData skey[2];
+		HeapTuple	tuple;
+		bool		replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert(HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false;	/* skip updating Oid
+														 * data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e57c7f..a7c906e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8799,7 +8799,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9067,7 +9068,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9469,7 +9471,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10480,6 +10483,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10500,7 +10505,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10529,6 +10533,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10537,7 +10543,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10585,6 +10590,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10593,7 +10600,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..9867b5e 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		trigger_deferrable = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +673,83 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				trigger_deferrable = pg_trigger->tgdeferrable;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * If this trigger has pending events, throw an error.
+			 */
+			if (trigger_deferrable && AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_IN_USE),
+						 errmsg("cannot replace \"%s\" on \"%s\" because it has pending trigger events",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
+			 * non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a costraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +777,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +810,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +827,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +954,56 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		HeapTuple	newtup;
+		TupleDesc	tupDesc;
+		bool		replaces[Natts_pg_trigger];
+
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false;	/* skip updating Oid
+															 * data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1046,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..d90c0bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5154b8..533d48d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5214,48 +5214,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e83329f..c0ddf2a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2428,6 +2428,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2439,7 +2442,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..44703dc 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,222 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a costraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+ERROR:  cannot replace "my_trig" on "my_table" because it has pending trigger events
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..11c5df7 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,204 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
#29Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#28)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Tue, Sep 8, 2020 at 9:36 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

I've fixed all except one point.

Thanks for addressing my previous review comments in your new v09 patch.

Those are fixed OK now, but I found 2 new review points.

====

COMMENT trigger.c (typo)

+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel)),
+ errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a
costraint trigger")));

Typo in the errhint text.
"costraint" -> "constraint"

====

COMMENT create_trigger.sgmg (add more help?)

I noticed that the CREATE OR REPLACE FUNCTION help [1]https://www.postgresql.org/docs/current/sql-createfunction.html describes the
OR REPLACE syntax ("Description" section) and also mentions some of
the restrictions when using REPLACE ("Notes" section).
[1]: https://www.postgresql.org/docs/current/sql-createfunction.html

~~
OTOH this trigger patch does not add anything much at all in the trigger help.

Shouldn't the "Description" at least say something like:
"CREATE OR REPLACE will either create a new trigger, or replace an
existing definition."

Shouldn't the "Notes" include information about restrictions when
using OR REPLACE
e.g. cannot replace triggers with triggers of a different kind
e.g. cannot replace triggers with pending events

What do you think?

====

Kind Regards,
Peter Smith.
Fujitsu Australia

#30osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#29)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi

Those are fixed OK now, but I found 2 new review points.

====

COMMENT trigger.c (typo)

+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel)),
+ errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a
costraint trigger")));

Typo in the errhint text.
"costraint" -> "constraint"

Fixed. Thank you.

====

COMMENT create_trigger.sgmg (add more help?)

I noticed that the CREATE OR REPLACE FUNCTION help [1] describes the OR
REPLACE syntax ("Description" section) and also mentions some of the
restrictions when using REPLACE ("Notes" section).
[1] - https://www.postgresql.org/docs/current/sql-createfunction.html

~~
OTOH this trigger patch does not add anything much at all in the trigger help.

Shouldn't the "Description" at least say something like:
"CREATE OR REPLACE will either create a new trigger, or replace an existing
definition."

Shouldn't the "Notes" include information about restrictions when using OR
REPLACE e.g. cannot replace triggers with triggers of a different kind e.g.
cannot replace triggers with pending events

What do you think?

That's a great idea. I've applied this idea to the latest patch v10.

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v10.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v10.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 289dd1d..feb30ab 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,15 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   When replacing an existing trigger with <command>CREATE OR
+   REPLACE TRIGGER</command>, there are restrictions. You cannot replace
+   triggers with a different type of trigger, that means it is impossible
+   to replace regular trigger with constraint trigger and vise versa.
+   Also, in a transaction you cannot replace triggers that
+   are already fired and have pending events.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..1a52fa3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2434,7 +2434,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 117e3fd..e546584 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1862,7 +1862,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1927,6 +1928,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1938,7 +1941,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0..3f35e50 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -165,9 +167,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -221,9 +226,57 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc conscan;
+		ScanKeyData skey[2];
+		HeapTuple	tuple;
+		bool		replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert(HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false;	/* skip updating Oid
+														 * data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e57c7f..a7c906e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8799,7 +8799,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9067,7 +9068,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9469,7 +9471,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10480,6 +10483,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10500,7 +10505,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10529,6 +10533,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10537,7 +10543,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10585,6 +10590,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10593,7 +10600,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..27e72df 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		trigger_deferrable = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +673,83 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				trigger_deferrable = pg_trigger->tgdeferrable;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * If this trigger has pending events, throw an error.
+			 */
+			if (trigger_deferrable && AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_IN_USE),
+						 errmsg("cannot replace \"%s\" on \"%s\" because it has pending trigger events",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
+			 * non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +777,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +810,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +827,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +954,56 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		HeapTuple	newtup;
+		TupleDesc	tupDesc;
+		bool		replaces[Natts_pg_trigger];
+
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false;	/* skip updating Oid
+															 * data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1046,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..d90c0bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5154b8..533d48d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5214,48 +5214,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e83329f..c0ddf2a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2428,6 +2428,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2439,7 +2442,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..c8bbcdf 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,222 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+ERROR:  cannot replace "my_trig" on "my_table" because it has pending trigger events
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..11c5df7 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,204 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
#31Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#30)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Wed, Sep 9, 2020 at 11:28 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

That's a great idea. I've applied this idea to the latest patch v10.

====

COMMENT create_trigger.sgml (typo/wording)

"vise versa" -> "vice versa"

BEFORE
You cannot replace triggers with a different type of trigger, that
means it is impossible to replace regular trigger with constraint
trigger and vise versa.

AFTER (suggestion)
You cannot replace triggers with a different type of trigger. That
means it is impossible to replace a regular trigger with a constraint
trigger, and vice versa.

====

Kind Regards,
Peter Smith.
Fujitsu Australia

#32osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#31)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hello, Peter-San

That's a great idea. I've applied this idea to the latest patch v10.

====

COMMENT create_trigger.sgml (typo/wording)

"vise versa" -> "vice versa"

Sorry and thank you for all your pointing out.

BEFORE
You cannot replace triggers with a different type of trigger, that means it is
impossible to replace regular trigger with constraint trigger and vise versa.

AFTER (suggestion)
You cannot replace triggers with a different type of trigger. That means it is
impossible to replace a regular trigger with a constraint trigger, and vice versa.

Thank you. Your suggestion must be better.

I attached the v11 patch.

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v11.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v11.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 289dd1d..cac927c 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,14 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   When replacing an existing trigger with <command>CREATE OR REPLACE
+   TRIGGER</command>, there are restrictions. You cannot replace triggers with
+   a different type of trigger. That means it is impossible to replace a regular
+   trigger with a constraint trigger, and vice versa. Also, in a transaction
+   you cannot replace triggers that are already fired and have pending events.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..1a52fa3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2434,7 +2434,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 117e3fd..e546584 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1862,7 +1862,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1927,6 +1928,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1938,7 +1941,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0..3f35e50 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -165,9 +167,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -221,9 +226,57 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc conscan;
+		ScanKeyData skey[2];
+		HeapTuple	tuple;
+		bool		replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert(HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false;	/* skip updating Oid
+														 * data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3e57c7f..a7c906e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8799,7 +8799,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9067,7 +9068,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9469,7 +9471,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10480,6 +10483,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10500,7 +10505,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10529,6 +10533,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10537,7 +10543,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10585,6 +10590,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10593,7 +10600,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..27e72df 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		trigger_deferrable = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +673,83 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				trigger_deferrable = pg_trigger->tgdeferrable;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * If this trigger has pending events, throw an error.
+			 */
+			if (trigger_deferrable && AfterTriggerPendingOnRel(RelationGetRelid(rel)))
+				ereport(ERROR,
+						(errcode(ERRCODE_OBJECT_IN_USE),
+						 errmsg("cannot replace \"%s\" on \"%s\" because it has pending trigger events",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
+			 * non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +777,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +810,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +827,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +954,56 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		HeapTuple	newtup;
+		TupleDesc	tupDesc;
+		bool		replaces[Natts_pg_trigger];
+
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false;	/* skip updating Oid
+															 * data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1046,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..d90c0bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5154b8..533d48d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5214,48 +5214,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e83329f..c0ddf2a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2428,6 +2428,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2439,7 +2442,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..c8bbcdf 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,222 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcA
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (2500);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+ERROR:  cannot replace "my_trig" on "my_table" because it has pending trigger events
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..11c5df7 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,204 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); --should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- trigger attached to the parent partition table is replaced by a new one to the parent partition table.
+-- verify that all partitioned table share the latter trigger in this case.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the parent partition table is replaced by a new one to the child partition table.
+-- verify that only the child partition's trigger is replaced and other tables' triggers aren't.
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the parent partition table.
+-- verify that the child partition's trigger is replaced by the new one as well and other partition share the same one.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- trigger attached to the child partition table is replaced by a new one to the child partition table.
+-- verify that the trigger of the child partition is replaced and no other partition has the partition.
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+insert into parted_trig (a) values (1500);
+insert into parted_trig (a) values (2500);
+truncate parted_trig;
+drop trigger my_trig on parted_trig_1;
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
+
+-- test for protection of dropping a trigger that has pending events in the session
+create table my_table (id integer);
+create function funcA () returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+begin;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcA();
+insert into my_table (id) values (1); -- make this trigger above pending
+create or replace constraint trigger my_trig
+  after insert on my_table
+  deferrable initially deferred
+  for each row execute procedure funcB(); -- should fail
+end;
+drop table my_table;
+drop function funcA();
+drop function funcB();
#33Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#32)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Thu, Sep 10, 2020 at 12:34 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

I attached the v11 patch.

The v11 patch looked OK to me.

Since I have no more review comments I am marking this as "ready for committer".

Kind Regards,
Peter Smith.
Fujitsu Australia

#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: osumi.takamichi@fujitsu.com (#32)
Re: extension patch of CREATE OR REPLACE TRIGGER

"osumi.takamichi@fujitsu.com" <osumi.takamichi@fujitsu.com> writes:

[ CREATE_OR_REPLACE_TRIGGER_v11.patch ]

I took a quick look through this. I think there's still work left to do.

* I'm concerned by the fact that there doesn't seem to be any defense
against somebody replacing a foreign-key trigger with something that
does something else entirely, and thereby silently breaking their
foreign key constraint. I think it might be a good idea to forbid
replacing triggers for which tgisinternal is true; but I've not done
the legwork to see if that's exactly the condition we want.

* In the same vein, I'm not sure that the right things happen when fooling
with triggers attached to partitioned tables. We presumably don't want to
allow mucking directly with a child trigger. Perhaps refusing an update
when tgisinternal might fix this too (although we'll have to be careful to
make the error message not too confusing).

* I don't think that you've fully thought through the implications
of replacing a trigger for a table that the current transaction has
already modified. Is it really sufficient, or even useful, to do
this:

+            /*
+             * If this trigger has pending events, throw an error.
+             */
+            if (trigger_deferrable && AfterTriggerPendingOnRel(RelationGetRelid(rel)))

As an example, if we change a BEFORE trigger to an AFTER trigger,
that's not going to affect the fact that we *already* fired that
trigger. Maybe this is okay and we just need to document it, but
I'm not convinced.

* BTW, I don't think a trigger necessarily has to be deferrable
in order to have pending AFTER events. The existing use of
AfterTriggerPendingOnRel certainly doesn't assume that. But really,
I think we probably ought to be applying CheckTableNotInUse which'd
include that test. (Another way in which there's fuzzy thinking
here is that AfterTriggerPendingOnRel isn't specific to *this*
trigger.)

* A lesser point is that I think you're overcomplicating the
code by applying heap_modify_tuple. You might as well just
build the new tuple normally in all cases, and then apply
either CatalogTupleInsert or CatalogTupleUpdate.

* Also, the search for an existing trigger tuple is being
done the hard way. You're using an index on (tgrelid, tgname),
so you could include the name in the index key and expect that
there's at most one match.

regards, tom lane

#35osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Tom Lane (#34)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi

I spent too much time to respond to this e-mail. Sorry.
Actually, I got stuck to deal with achieving both
error detection of internal trigger case and pending trigger case.

* I'm concerned by the fact that there doesn't seem to be any defense against
somebody replacing a foreign-key trigger with something that does something
else entirely, and thereby silently breaking their foreign key constraint. I think
it might be a good idea to forbid replacing triggers for which tgisinternal is true;
but I've not done the legwork to see if that's exactly the condition we want.

* In the same vein, I'm not sure that the right things happen when fooling with
triggers attached to partitioned tables. We presumably don't want to allow
mucking directly with a child trigger. Perhaps refusing an update when
tgisinternal might fix this too (although we'll have to be careful to make the error
message not too confusing).

Yeah, you are right. tgisinternal works to detect an invalid cases.
I added a new check condition in my patch to prohibit
the replacement of internal triggers by an user,
which protects FK trigger and child trigger from being replaced directly.

* I don't think that you've fully thought through the implications of replacing a
trigger for a table that the current transaction has already modified. Is it really
sufficient, or even useful, to do
this:

+            /*
+             * If this trigger has pending events, throw an error.
+             */
+            if (trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))

As an example, if we change a BEFORE trigger to an AFTER trigger, that's not
going to affect the fact that we *already* fired that trigger. Maybe this is okay
and we just need to document it, but I'm not convinced.

* BTW, I don't think a trigger necessarily has to be deferrable in order to have
pending AFTER events. The existing use of AfterTriggerPendingOnRel
certainly doesn't assume that. But really, I think we probably ought to be
applying CheckTableNotInUse which'd include that test. (Another way in
which there's fuzzy thinking here is that AfterTriggerPendingOnRel isn't specific
to *this*
trigger.)

Hmm, actually, when I just put a code of CheckTableNotInUse() in CreateTrigger(),
it throws error like "cannot CREATE OR REPLACE
TRIGGER because it is being used by active queries in this session".
This causes an break of the protection for internal cases above
and a contradiction of already passed test cases.
I though adding a condition of in_partition==false to call
CheckTableNotInUse(). But this doesn't work in a corner case that
child trigger generated internally is pending and
we don't want to allow the replacement of this kind of trigger.
Did you have any good idea to achieve both points at the same time ?

* A lesser point is that I think you're overcomplicating the code by applying
heap_modify_tuple. You might as well just build the new tuple normally in all
cases, and then apply either CatalogTupleInsert or CatalogTupleUpdate.

* Also, the search for an existing trigger tuple is being done the hard way.
You're using an index on (tgrelid, tgname), so you could include the name in the
index key and expect that there's at most one match.

While waiting for a new reply, I'll doing those 2 refactorings.

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v12.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v12.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..2e508db 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,14 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   When replacing an existing trigger with <command>CREATE OR REPLACE
+   TRIGGER</command>, there are restrictions. You cannot replace triggers with
+   a different type of trigger. That means it is impossible to replace a regular
+   trigger with a constraint trigger, and vice versa. Also, in a transaction
+   you cannot replace triggers that are already fired and have pending events.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..1a52fa3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2434,7 +2434,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0974f3e..5ccebb9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1862,7 +1862,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1927,6 +1928,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1938,7 +1941,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0..3f35e50 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -165,9 +167,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -221,9 +226,57 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc conscan;
+		ScanKeyData skey[2];
+		HeapTuple	tuple;
+		bool		replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert(HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false;	/* skip updating Oid
+														 * data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e0ac4e0..a2a1c4d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8832,7 +8832,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9100,7 +9101,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9502,7 +9504,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10513,6 +10516,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10533,7 +10538,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10562,6 +10566,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10570,7 +10576,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10618,6 +10623,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10626,7 +10633,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..49ca88f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -175,7 +176,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		internal_trigger = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +673,86 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), stmt->trigname) == 0)
+			{
+				/* Store values for replacement of this trigger */
+				trigoid = pg_trigger->oid;
+				existing_constraint_oid = pg_trigger->tgconstraint;
+				trigger_exists = true;
+				internal_trigger = pg_trigger->tgisinternal;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * An internal trigger cannot be replaced by another user defined
+			 * trigger. This should exclude the case that internal trigger is
+			 * child trigger of partition table and needs to be rewritten when
+			 * the parent trigger is replaced by user.
+			 */
+			if (internal_trigger && isInternal == false && in_partition == false)
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
+			 * non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +780,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +813,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +830,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -911,12 +957,56 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 
 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
 
-	/*
-	 * Insert tuple into pg_trigger.
-	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+		/*
+		 * Insert tuple into pg_trigger.
+		 */
+		CatalogTupleInsert(tgrel, tuple);
+
+		heap_freetuple(tuple);
+	}
+	else
+	{
+		HeapTuple	newtup;
+		TupleDesc	tupDesc;
+		bool		replaces[Natts_pg_trigger];
+
+		memset(replaces, true, sizeof(replaces));
+
+		ScanKeyInit(&key,
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									NULL, 1, &key);
+		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
+			{
+				tupDesc = RelationGetDescr(tgrel);
+				replaces[Anum_pg_trigger_oid - 1] = false;	/* skip updating Oid
+															 * data */
+				replaces[Anum_pg_trigger_tgrelid - 1] = false;
+				replaces[Anum_pg_trigger_tgname - 1] = false;
+				trigoid = pg_trigger->oid;
+				newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+
+				/* Update tuple in pg_trigger */
+				CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+
+				heap_freetuple(newtup);
+				is_update = true;
+				break;
+			}
+		}
+		systable_endscan(tgscan);
+	}
 
-	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
 
 	pfree(DatumGetPointer(values[Anum_pg_trigger_tgname - 1]));
@@ -959,6 +1049,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..d90c0bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0d101d8..ef9fe03 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,48 +5211,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45..346412e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2429,6 +2429,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2440,7 +2443,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..537403a 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,155 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..fc8da77 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,148 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
#36osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: osumi.takamichi@fujitsu.com (#35)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hello

* A lesser point is that I think you're overcomplicating the code by
applying heap_modify_tuple. You might as well just build the new
tuple normally in all cases, and then apply either CatalogTupleInsert or

CatalogTupleUpdate.

* Also, the search for an existing trigger tuple is being done the hard way.
You're using an index on (tgrelid, tgname), so you could include the
name in the index key and expect that there's at most one match.

While waiting for a new reply, I'll doing those 2 refactorings.

I'm done with those refactorings. Please have a look at the changes
of the latest patch.

* I don't think that you've fully thought through the implications of
replacing a trigger for a table that the current transaction has
already modified. Is it really sufficient, or even useful, to do
this:

+            /*
+             * If this trigger has pending events, throw an error.
+             */
+            if (trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))

As an example, if we change a BEFORE trigger to an AFTER trigger,
that's not going to affect the fact that we *already* fired that
trigger. Maybe this is okay and we just need to document it, but I'm not

convinced.

* BTW, I don't think a trigger necessarily has to be deferrable in
order to have pending AFTER events. The existing use of
AfterTriggerPendingOnRel certainly doesn't assume that. But really, I
think we probably ought to be applying CheckTableNotInUse which'd
include that test. (Another way in which there's fuzzy thinking here
is that AfterTriggerPendingOnRel isn't specific to *this*
trigger.)

Hmm, actually, when I just put a code of CheckTableNotInUse() in
CreateTrigger(), it throws error like "cannot CREATE OR REPLACE TRIGGER
because it is being used by active queries in this session".
This causes an break of the protection for internal cases above and a
contradiction of already passed test cases.
I though adding a condition of in_partition==false to call CheckTableNotInUse().
But this doesn't work in a corner case that child trigger generated internally is
pending and we don't want to allow the replacement of this kind of trigger.
Did you have any good idea to achieve both points at the same time ?

Still, in terms of this point, I'm waiting for a comment !

Regards,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v13.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v13.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..2e508db 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,14 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   When replacing an existing trigger with <command>CREATE OR REPLACE
+   TRIGGER</command>, there are restrictions. You cannot replace triggers with
+   a different type of trigger. That means it is impossible to replace a regular
+   trigger with a constraint trigger, and vice versa. Also, in a transaction
+   you cannot replace triggers that are already fired and have pending events.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa..1a52fa3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2434,7 +2434,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 	 * Create the Check Constraint
 	 */
 	constrOid =
-		CreateConstraintEntry(ccname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  ccname,	/* Constraint Name */
 							  RelationGetNamespace(rel),	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0974f3e..5ccebb9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1862,7 +1862,8 @@ index_constraint_create(Relation heapRelation,
 	/*
 	 * Construct a pg_constraint entry.
 	 */
-	conOid = CreateConstraintEntry(constraintName,
+	conOid = CreateConstraintEntry(InvalidOid,
+								   constraintName,
 								   namespaceId,
 								   constraintType,
 								   deferrable,
@@ -1927,6 +1928,8 @@ index_constraint_create(Relation heapRelation,
 		CreateTrigStmt *trigger;
 
 		trigger = makeNode(CreateTrigStmt);
+		trigger->replace = false;
+		trigger->isconstraint = true;
 		trigger->trigname = (constraintType == CONSTRAINT_PRIMARY) ?
 			"PK_ConstraintTrigger" :
 			"Unique_ConstraintTrigger";
@@ -1938,7 +1941,6 @@ index_constraint_create(Relation heapRelation,
 		trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
 		trigger->columns = NIL;
 		trigger->whenClause = NULL;
-		trigger->isconstraint = true;
 		trigger->deferrable = true;
 		trigger->initdeferred = initdeferred;
 		trigger->constrrel = NULL;
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0..3f35e50 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -44,10 +44,12 @@
  * constraint) are *not* created here.  But we do make dependency links
  * from the constraint to the things it depends on.
  *
- * The new constraint's OID is returned.
+ * The new constraint's OID is returned. (This will be the same as
+ * "existing_constraint_oid" if that is other than InvalidOid)
  */
 Oid
-CreateConstraintEntry(const char *constraintName,
+CreateConstraintEntry(Oid existing_constraint_oid,
+					  const char *constraintName,
 					  Oid constraintNamespace,
 					  char constraintType,
 					  bool isDeferrable,
@@ -78,7 +80,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool is_internal)
 {
 	Relation	conDesc;
-	Oid			conOid;
+	Oid			conOid = InvalidOid;
 	HeapTuple	tup;
 	bool		nulls[Natts_pg_constraint];
 	Datum		values[Natts_pg_constraint];
@@ -165,9 +167,12 @@ CreateConstraintEntry(const char *constraintName,
 		values[i] = (Datum) NULL;
 	}
 
-	conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
-								Anum_pg_constraint_oid);
-	values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	if (!OidIsValid(existing_constraint_oid))
+	{
+		conOid = GetNewOidWithIndex(conDesc, ConstraintOidIndexId,
+									Anum_pg_constraint_oid);
+		values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(conOid);
+	}
 	values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
 	values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
 	values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
@@ -221,9 +226,57 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conbin - 1] = true;
 
-	tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+	if (OidIsValid(existing_constraint_oid))
+	{
+		/*
+		 * Replace the existing constraint entry.
+		 */
+		SysScanDesc conscan;
+		ScanKeyData skey[2];
+		HeapTuple	tuple;
+		bool		replaces[Natts_pg_constraint];
+		Form_pg_constraint constrForm;
+
+		ScanKeyInit(&skey[0],
+					Anum_pg_constraint_oid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(existing_constraint_oid));
+
+		conscan = systable_beginscan(conDesc,
+									 ConstraintOidIndexId,
+									 true,
+									 NULL,
+									 1,
+									 skey);
 
-	CatalogTupleInsert(conDesc, tup);
+		tuple = systable_getnext(conscan);
+		Assert(HeapTupleIsValid(tuple));
+		constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+		conOid = constrForm->oid;
+		Assert(conOid == existing_constraint_oid);
+
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_constraint_oid - 1] = false;	/* skip updating Oid
+														 * data */
+		replaces[Anum_pg_constraint_conname - 1] = false;
+		replaces[Anum_pg_constraint_confrelid - 1] = false;
+
+		/* Modify the existing constraint entry */
+		tup = heap_modify_tuple(tuple, RelationGetDescr(conDesc), values, nulls, replaces);
+		CatalogTupleUpdate(conDesc, &tuple->t_self, tup);
+		heap_freetuple(tup);
+
+		/* Remove all old dependencies before registering new ones */
+		deleteDependencyRecordsFor(ConstraintRelationId, conOid, true);
+
+		systable_endscan(conscan);
+	}
+	else
+	{
+		tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
+		CatalogTupleInsert(conDesc, tup);
+		heap_freetuple(tup);
+	}
 
 	ObjectAddressSet(conobject, ConstraintRelationId, conOid);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e0ac4e0..a2a1c4d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8832,7 +8832,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 	/*
 	 * Record the FK constraint in pg_constraint.
 	 */
-	constrOid = CreateConstraintEntry(conname,
+	constrOid = CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(rel),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9100,7 +9101,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			else
 				conname = fkconstraint->conname;
 			constrOid =
-				CreateConstraintEntry(conname,
+				CreateConstraintEntry(InvalidOid,
+									  conname,
 									  RelationGetNamespace(partition),
 									  CONSTRAINT_FOREIGN,
 									  fkconstraint->deferrable,
@@ -9502,7 +9504,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 
 		indexOid = constrForm->conindid;
 		constrOid =
-			CreateConstraintEntry(fkconstraint->conname,
+			CreateConstraintEntry(InvalidOid,
+								  fkconstraint->conname,
 								  constrForm->connamespace,
 								  CONSTRAINT_FOREIGN,
 								  fkconstraint->deferrable,
@@ -10513,6 +10516,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10533,7 +10538,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10562,6 +10566,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10570,7 +10576,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10618,6 +10623,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10626,7 +10633,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 672fccf..df1266b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -167,15 +168,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -184,6 +184,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		internal_trigger = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -668,6 +672,93 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			internal_trigger = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * An internal trigger cannot be replaced by another user defined
+			 * trigger. This should exclude the case that internal trigger is
+			 * child trigger of partition table and needs to be rewritten when
+			 * the parent trigger is replaced by user.
+			 */
+			if (internal_trigger && isInternal == false && in_partition == false)
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE CONSTRAINT TRIGGER command can't replace
+			 * non-constraint trigger.
+			 */
+			if (stmt->isconstraint && !OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a regular trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE TRIGGER to replace a regular trigger")));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (!stmt->isconstraint && OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel)),
+						 errhint("use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger")));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -695,7 +786,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	{
 		/* Internal callers should have made their own constraints */
 		Assert(!isInternal);
-		constraintOid = CreateConstraintEntry(stmt->trigname,
+		constraintOid = CreateConstraintEntry(existing_constraint_oid,
+											  stmt->trigname,
 											  RelationGetNamespace(rel),
 											  CONSTRAINT_TRIGGER,
 											  stmt->deferrable,
@@ -727,15 +819,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -753,37 +836,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -909,12 +961,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		is_update = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -959,6 +1028,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65..8e25349 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3100,7 +3100,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 	 * Store the constraint in pg_constraint
 	 */
 	ccoid =
-		CreateConstraintEntry(constr->conname,	/* Constraint Name */
+		CreateConstraintEntry(InvalidOid,
+							  constr->conname,	/* Constraint Name */
 							  domainNamespace,	/* namespace */
 							  CONSTRAINT_CHECK, /* Constraint Type */
 							  false,	/* Is Deferrable */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40..d90c0bf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4315,6 +4315,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4323,7 +4325,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168..4a9f0e3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,48 +5211,50 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
 					n->constrrel = NULL;
 					$$ = (Node *)n;
 				}
-			| CREATE CONSTRAINT TRIGGER name AFTER TriggerEvents ON
+			| CREATE opt_or_replace CONSTRAINT TRIGGER name AFTER TriggerEvents ON
 			qualified_name OptConstrFromTable ConstraintAttributeSpec
 			FOR EACH ROW TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $4;
-					n->relation = $8;
-					n->funcname = $17;
-					n->args = $19;
+					n->replace = $2;
+					n->trigname = $5;
+					n->relation = $9;
+					n->funcname = $18;
+					n->args = $20;
 					n->row = true;
 					n->timing = TRIGGER_TYPE_AFTER;
-					n->events = intVal(linitial($6));
-					n->columns = (List *) lsecond($6);
-					n->whenClause = $14;
+					n->events = intVal(linitial($7));
+					n->columns = (List *) lsecond($7);
+					n->whenClause = $15;
 					n->transitionRels = NIL;
 					n->isconstraint  = true;
-					processCASbits($10, @10, "TRIGGER",
+					processCASbits($11, @11, "TRIGGER",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
-					n->constrrel = $9;
+					n->constrrel = $10;
 					$$ = (Node *)n;
 				}
 		;
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece..0d938f1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -182,7 +182,8 @@ typedef enum ConstraintCategory
 } ConstraintCategory;
 
 
-extern Oid	CreateConstraintEntry(const char *constraintName,
+extern Oid	CreateConstraintEntry(Oid existing_constraint_oid,
+								  const char *constraintName,
 								  Oid constraintNamespace,
 								  char constraintType,
 								  bool isDeferrable,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45..346412e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2429,6 +2429,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2440,7 +2443,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 5e76b3a..537403a 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3052,3 +3052,155 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a regular trigger
+HINT:  use CREATE OR REPLACE TRIGGER to replace a regular trigger
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+HINT:  use CREATE OR REPLACE CONSTRAINT TRIGGER to replace a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index e228d0a..fc8da77 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2295,3 +2295,148 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
#37tsunakawa.takay@fujitsu.com
tsunakawa.takay@fujitsu.com
In reply to: osumi.takamichi@fujitsu.com (#36)
RE: extension patch of CREATE OR REPLACE TRIGGER

From: osumi.takamichi@fujitsu.com <osumi.takamichi@fujitsu.com>

* I don't think that you've fully thought through the implications
of replacing a trigger for a table that the current transaction has
already modified. Is it really sufficient, or even useful, to do
this:

+            /*
+             * If this trigger has pending events, throw an error.
+             */
+            if (trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))

As an example, if we change a BEFORE trigger to an AFTER trigger,
that's not going to affect the fact that we *already* fired that
trigger. Maybe this is okay and we just need to document it, but
I'm not

convinced.

* BTW, I don't think a trigger necessarily has to be deferrable in
order to have pending AFTER events. The existing use of
AfterTriggerPendingOnRel certainly doesn't assume that. But really,
I think we probably ought to be applying CheckTableNotInUse which'd
include that test. (Another way in which there's fuzzy thinking
here is that AfterTriggerPendingOnRel isn't specific to *this*
trigger.)

Hmm, actually, when I just put a code of CheckTableNotInUse() in
CreateTrigger(), it throws error like "cannot CREATE OR REPLACE
TRIGGER because it is being used by active queries in this session".
This causes an break of the protection for internal cases above and a
contradiction of already passed test cases.
I though adding a condition of in_partition==false to call

CheckTableNotInUse().

But this doesn't work in a corner case that child trigger generated
internally is pending and we don't want to allow the replacement of this kind

of trigger.

Did you have any good idea to achieve both points at the same time ?

Still, in terms of this point, I'm waiting for a comment !

I understand this patch is intended for helping users to migrate from other DBMSs (mainly Oracle?) because they can easily alter some trigger attributes (probably the trigger action and WHEN condition in practice.) OTOH, the above issue seems to be associated with the Postgres-specific constraint trigger that is created with CONSTRAINT clause. (Oracle and the SQL standard doesn't have an equivalent feature.)

So, how about just disallowing the combination of REPLACE and CONSTRAINT? I think nobody would be crippled with that. If someone wants the combination by all means, that can be a separate enhancement.

Regards
Takayuki Tsunakawa

#38osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: tsunakawa.takay@fujitsu.com (#37)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi,

From: Tsunakawa, Takayuki < tsunakawa.takay@fujitsu.com>

From: osumi.takamichi@fujitsu.com <osumi.takamichi@fujitsu.com>

* I don't think that you've fully thought through the implications
of replacing a trigger for a table that the current transaction
has already modified. Is it really sufficient, or even useful, to
do
this:

+            /*
+             * If this trigger has pending events, throw an error.
+             */
+            if (trigger_deferrable &&
+ AfterTriggerPendingOnRel(RelationGetRelid(rel)))

As an example, if we change a BEFORE trigger to an AFTER trigger,
that's not going to affect the fact that we *already* fired that
trigger. Maybe this is okay and we just need to document it, but
I'm not

convinced.

* BTW, I don't think a trigger necessarily has to be deferrable in
order to have pending AFTER events. The existing use of
AfterTriggerPendingOnRel certainly doesn't assume that. But
really, I think we probably ought to be applying
CheckTableNotInUse which'd include that test. (Another way in
which there's fuzzy thinking here is that AfterTriggerPendingOnRel
isn't specific to *this*
trigger.)

Hmm, actually, when I just put a code of CheckTableNotInUse() in
CreateTrigger(), it throws error like "cannot CREATE OR REPLACE
TRIGGER because it is being used by active queries in this session".
This causes an break of the protection for internal cases above and
a contradiction of already passed test cases.
I though adding a condition of in_partition==false to call

CheckTableNotInUse().

But this doesn't work in a corner case that child trigger generated
internally is pending and we don't want to allow the replacement of
this kind

of trigger.

Did you have any good idea to achieve both points at the same time ?

Still, in terms of this point, I'm waiting for a comment !

I understand this patch is intended for helping users to migrate from other
DBMSs (mainly Oracle?) because they can easily alter some trigger attributes
(probably the trigger action and WHEN condition in practice.) OTOH, the
above issue seems to be associated with the Postgres-specific constraint
trigger that is created with CONSTRAINT clause. (Oracle and the SQL
standard doesn't have an equivalent feature.)

So, how about just disallowing the combination of REPLACE and
CONSTRAINT? I think nobody would be crippled with that. If someone
wants the combination by all means, that can be a separate enhancement.

I didn't notice this kind of perspective and you are right.
In order to achieve the purpose to help database migration from Oracle to Postgres,
prohibitting the usage of OR REPLACE for constraint trigger is no problem.

Thanks for your great advice. I fixed and created new version.
Also, the size of this patch becomes much smaller.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v14.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v14.patchDownload
diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..23783f9 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing regular trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing regular trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,13 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   When replacing an existing trigger with <command>CREATE OR REPLACE
+   TRIGGER</command>, there are restrictions. You cannot replace constraint triggers.
+   That means it is impossible to replace a regular trigger with a constraint trigger
+   and to replace a constraint trigger with another constraint trigger.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a29c14b..d883e58 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10518,6 +10518,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10538,7 +10540,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10567,6 +10568,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10575,7 +10578,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10623,6 +10625,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10631,7 +10635,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac16..d142d13 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -168,15 +169,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		is_update = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		internal_trigger = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -669,6 +673,81 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			internal_trigger = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+		else
+		{
+			/*
+			 * An internal trigger cannot be replaced by another user defined
+			 * trigger. This should exclude the case that internal trigger is
+			 * child trigger of partition table and needs to be rewritten when
+			 * the parent trigger is replaced by user.
+			 */
+			if (internal_trigger && isInternal == false && in_partition == false)
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+								stmt->trigname, RelationGetRelationName(rel))));
+
+			/*
+			 * CREATE OR REPLACE TRIGGER command can't replace constraint
+			 * trigger.
+			 */
+			if (OidIsValid(existing_constraint_oid))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+								stmt->trigname, RelationGetRelationName(rel))));
+		}
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -728,15 +807,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -754,37 +824,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -910,12 +949,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		is_update = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -960,6 +1016,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (is_update)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d765..4b07617 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4313,6 +4313,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4321,7 +4323,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168..7dbaf67 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,21 +5211,22 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
@@ -5238,6 +5239,7 @@ CreateTrigStmt:
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
+					n->replace = false;
 					n->trigname = $4;
 					n->relation = $8;
 					n->funcname = $17;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45..346412e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2429,6 +2429,9 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* when true, replace trigger if already
+								 * exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2440,7 +2443,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c19aac9..b9fdee2 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3140,3 +3140,169 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index bf2e73a..5588655 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2348,3 +2348,162 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA(); -- should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
#39Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#38)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hello Osumi-san.

Below are my v14 patch review comments for your consideration.

===

(1) COMMENT
File: NA
Maybe next time consider using format-patch to make the patch. Then
you can include a comment to give the background/motivation for this
change.

===

(2) COMMENT
File: doc/src/sgml/ref/create_trigger.sgml
@@ -446,6 +454,13 @@ UPDATE OF <replaceable>column_name1</replaceable>
[, <replaceable>column_name2</
Currently it says:
When replacing an existing trigger with CREATE OR REPLACE TRIGGER,
there are restrictions. You cannot replace constraint triggers. That
means it is impossible to replace a regular trigger with a constraint
trigger and to replace a constraint trigger with another constraint
trigger.

--

Is that correct wording? I don't think so. Saying "to replace a
regular trigger with a constraint trigger" is NOT the same as "replace
a constraint trigger".

Maybe I am mistaken but I think the help and the code are no longer in
sync anymore. e.g. In previous versions of this patch you used to
verify replacement trigger kinds (regular/constraint) match. AFAIK you
are not doing that in the current code (but you should be). So
although you say "impossible to replace a regular trigger with a
constraint trigger" I don't see any code to check/enforce that ( ?? )

IMO when you simplified this v14 patch you may have removed some extra
trigger kind conditions that should not have been removed.

Also, the test code should have detected this problem, but I think the
tests have also been broken in v14. See later COMMENT (9).

===

(3) COMMENT
File: src/backend/commands/trigger.c
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ bool internal_trigger = false;

--

There is potential for confusion of "isInternal" versus
"internal_trigger". The meaning is not apparent from the names, but
IIUC isInternal seems to be when creating an internal trigger, whereas
internal_trigger seems to be when you found an existing trigger that
was previously created as isInternal.

Maybe something like "existing_isInternal" would be a better name
instead of "internal_trigger".

===

(4) COMMENT
File: src/backend/commands/trigger.c
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ bool is_update = false;

Consider if "was_replaced" might be a better name than "is_update".

===

(5) COMMENT
File: src/backend/commands/trigger.c
@@ -669,6 +673,81 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+ else
+ {
+ /*
+ * An internal trigger cannot be replaced by another user defined
+ * trigger. This should exclude the case that internal trigger is
+ * child trigger of partition table and needs to be rewritten when
+ * the parent trigger is replaced by user.
+ */
+ if (internal_trigger && isInternal == false && in_partition == false)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * CREATE OR REPLACE TRIGGER command can't replace constraint
+ * trigger.
+ */
+ if (OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ }

It is not really necessary for the "OR REPLACE" code to need to be
inside an "else" block like that because the "if (!stmt->replace)" has
already been tested above. Consider removing the "else {" to remove
unnecessary indent if you want to.

===

(6) COMMENT
File: src/backend/commands/trigger.c
(same code block as above)

Condition is strangely written:
e.g.
Before: if (internal_trigger && isInternal == false && in_partition == false)
After: if (internal_trigger && !isInternal && !in_partition)

===

(7) COMMENT
File: src/backend/commands/trigger.c
(same code block as above)
/*
* CREATE OR REPLACE TRIGGER command can't replace constraint
* trigger.
*/

--

Only need to say
/* Can't replace a constraint trigger. */

===

(8) COMMENT
File: src/include/nodes/parsenodes.h
@@ -2429,6 +2429,9 @@ typedef struct CreateAmStmt

The comment does not need to say "when true,". Just saying "replace
trigger if already exists" is enough.

===

(9) COMMENT
File: src/test/regress/expected/triggers.out
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists

--

I think this test has been broken in v14. That last "create constraint
trigger my_trig" above can never be expected to work simply because
you are not specifying the "OR REPLACE" syntax. So in fact this is not
properly testing for incompatible types at all. It needs to say
"create OR REPLACE constraint trigger my_trig" to be testing what it
claims to be testing.

I also think there is a missing check in the code - see COMMENT (2) -
for handling this scenario. But since this test case is broken you do
not then notice the code check is missing.

===

Kind Regards,
Peter Smith.
Fujitsu Australia

#40osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#39)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi

Peter-San, thanks for your support.
On Monday, November 2, 2020 2:39 PM Peter Smith wrote:

===

(1) COMMENT
File: NA
Maybe next time consider using format-patch to make the patch. Then you
can include a comment to give the background/motivation for this change.

OK. How about v15 ?

===

(2) COMMENT
File: doc/src/sgml/ref/create_trigger.sgml
@@ -446,6 +454,13 @@ UPDATE OF
<replaceable>column_name1</replaceable>
[, <replaceable>column_name2</
Currently it says:
When replacing an existing trigger with CREATE OR REPLACE TRIGGER,
there are restrictions. You cannot replace constraint triggers. That means it is
impossible to replace a regular trigger with a constraint trigger and to replace
a constraint trigger with another constraint trigger.

--

Is that correct wording? I don't think so. Saying "to replace a regular trigger
with a constraint trigger" is NOT the same as "replace a constraint trigger".

I corrected my wording in create_trigger.sgml, which should cause less confusion
than v14. The reason why I changed the documents is described below.

Maybe I am mistaken but I think the help and the code are no longer in sync
anymore. e.g. In previous versions of this patch you used to verify
replacement trigger kinds (regular/constraint) match. AFAIK you are not
doing that in the current code (but you should be). So although you say
"impossible to replace a regular trigger with a constraint trigger" I don't see
any code to check/enforce that ( ?? )
IMO when you simplified this v14 patch you may have removed some extra
trigger kind conditions that should not have been removed.

Also, the test code should have detected this problem, but I think the tests
have also been broken in v14. See later COMMENT (9).

Don't worry and those are not broken.

I changed some codes in gram.y to throw a syntax error when
OR REPLACE clause is used with CREATE CONSTRAINT TRIGGER.

In the previous discussion with Tsunakawa-San in this thread,
I judged that OR REPLACE clause is
not necessary for *CONSTRAINT* TRIGGER to achieve the purpose of this patch.
It is to support the database migration from Oracle to Postgres
by supporting the same syntax for trigger replacement. Here,
because the constraint trigger is unique to the latter,
I prohibited the usage of CREATE CONSTRAINT TRIGGER and OR REPLACE
clauses at the same time at the grammatical level.
Did you agree with this way of modification ?

To prohibit the combination OR REPLACE and CONSTRAINT clauses may seem
a little bit radical but I refer to an example of the combination to use
CREATE CONSTRAINT TRIGGER and AFTER clause.
When the timing of trigger is not AFTER for CONSTRAINT TRIGGER,
an syntax error is caused. I learnt and followed the way for
my modification from it.

===

(3) COMMENT
File: src/backend/commands/trigger.c
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ bool internal_trigger = false;

--

There is potential for confusion of "isInternal" versus "internal_trigger". The
meaning is not apparent from the names, but IIUC isInternal seems to be
when creating an internal trigger, whereas internal_trigger seems to be when
you found an existing trigger that was previously created as isInternal.

Maybe something like "existing_isInternal" would be a better name instead of
"internal_trigger".

Definitely sounds better. I fixed the previous confusing name.

===

(4) COMMENT
File: src/backend/commands/trigger.c
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ bool is_update = false;

Consider if "was_replaced" might be a better name than "is_update".

===

Also, this must be good. Done.

(5) COMMENT
File: src/backend/commands/trigger.c
@@ -669,6 +673,81 @@ CreateTrigger(CreateTrigStmt *stmt, const char
*queryString,
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" already exists",
+ stmt->trigname, RelationGetRelationName(rel))));
+ else
+ {
+ /*
+ * An internal trigger cannot be replaced by another user defined
+ * trigger. This should exclude the case that internal trigger is
+ * child trigger of partition table and needs to be rewritten when
+ * the parent trigger is replaced by user.
+ */
+ if (internal_trigger && isInternal == false && in_partition == false)
+ ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+
+ /*
+ * CREATE OR REPLACE TRIGGER command can't replace constraint
+ * trigger.
+ */
+ if (OidIsValid(existing_constraint_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+ stmt->trigname, RelationGetRelationName(rel))));
+ }

It is not really necessary for the "OR REPLACE" code to need to be inside an
"else" block like that because the "if (!stmt->replace)" has already been
tested above. Consider removing the "else {" to remove unnecessary indent if
you want to.

Yeah, you are right. Fixed.

===

(6) COMMENT
File: src/backend/commands/trigger.c
(same code block as above)

Condition is strangely written:
e.g.
Before: if (internal_trigger && isInternal == false && in_partition == false)
After: if (internal_trigger && !isInternal && !in_partition)

OK. Done

===

(7) COMMENT
File: src/backend/commands/trigger.c
(same code block as above)
/*
* CREATE OR REPLACE TRIGGER command can't replace constraint
* trigger.
*/

--

Only need to say
/* Can't replace a constraint trigger. */

===

(8) COMMENT
File: src/include/nodes/parsenodes.h
@@ -2429,6 +2429,9 @@ typedef struct CreateAmStmt

The comment does not need to say "when true,". Just saying "replace trigger
if already exists" is enough.

===

Applied.

(9) COMMENT
File: src/test/regress/expected/triggers.out
+-- test for detecting incompatible replacement of trigger create table
+my_table (id integer); create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$ begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); create constraint trigger
+my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists

--

I think this test has been broken in v14. That last "create constraint trigger
my_trig" above can never be expected to work simply because you are not
specifying the "OR REPLACE" syntax.

As I described above, the grammatical error occurs to use
"CREATE OR REPLACE CONSTRAINT TRIGGER" in v14 (and v15 also).
At the time to write v14, I wanted to list up all imcompatible cases
even if some tests did *not* or can *not* contain "OR REPLACE" clause.
I think this way of change seemed broken to you.

Still now I think it's a good idea to cover such confusing cases,
so I didn't remove both failure tests in v15
(1) CREATE OR REPLACE TRIGGER creates a regular trigger and execute
CREATE CONSTRAINT TRIGGER, which should fail
(2) CREATE CONSTRAINT TRIGGER creates a constraint trigger and
execute CREATE OR REPLACE TRIGGER, which should fail
in order to show in such cases, the detection of error nicely works.
The results of tests are fine.

So in fact this is not properly testing for
incompatible types at all. It needs to say "create OR REPLACE constraint
trigger my_trig" to be testing what it claims to be testing.

I also think there is a missing check in the code - see COMMENT (2) - for
handling this scenario. But since this test case is broken you do not then
notice the code check is missing.

===

My inappropriate explanation especially in the create_trigger.sgml
made you think those are broken. But, as I said they are necessary still
to cover corner combination cases.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v15.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v15.patchDownload
From 6111e79b8b3f90c3ef0edb2507246d9019928b9f Mon Sep 17 00:00:00 2001
From: Osumi Takamichi <osumi.takamichi@jp.fujitsu.com>
Date: Wed, 4 Nov 2020 02:58:16 +0000
Subject: [PATCH v15] support CREATE OR REPLACE TRIGGER

This patch extends the grammer of CREATE TRIGGER to
accept OR REPLACE clause in order to replace an
exisiting regular trigger. The purpose of this patch
is to support migration from Oracle Database to Postgres
by giving compatible syntax for trigger replacement.
This allows user to replace a regular existing trigger
while it doesn't allow them to replace a constraint
existing trigger, which is unique to Postgres.

Author: Takamichi Osumi <osumi.takamichi@jp.fujitsu.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Tsunakawa, Takayuki <tsunakawa.takay@fujitsu.com>
Discussion: https://www.postgresql.org/message-id/0DDF369B45A1B44B8A687ED43F06557C010BC362%40G01JPEXMBYT03
---
 doc/src/sgml/ref/create_trigger.sgml   |  20 +++-
 src/backend/commands/tablecmds.c       |   9 +-
 src/backend/commands/trigger.c         | 154 +++++++++++++++++++++---------
 src/backend/nodes/copyfuncs.c          |   3 +-
 src/backend/nodes/equalfuncs.c         |   3 +-
 src/backend/parser/gram.y              |  24 ++---
 src/include/nodes/parsenodes.h         |   3 +-
 src/test/regress/expected/triggers.out | 166 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 159 +++++++++++++++++++++++++++++++
 9 files changed, 475 insertions(+), 66 deletions(-)

diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..a2f76ea 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing regular trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing regular trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,12 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   <command>CREATE OR REPLACE TRIGGER</command> works only for replacing a regular trigger.
+   That means it is impossible to replace a regular trigger with a constraint trigger
+   and to replace a constraint trigger with another constraint trigger.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a29c14b..d883e58 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10518,6 +10518,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10538,7 +10540,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10567,6 +10568,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10575,7 +10578,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10623,6 +10625,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10631,7 +10635,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac16..4f0f583 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -168,15 +169,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		was_replaced = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		existing_isInternal = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -669,6 +673,78 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			existing_isInternal = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * An internal trigger cannot be replaced by another user defined
+		 * trigger. This should exclude the case that internal trigger is
+		 * child trigger of partition table and needs to be rewritten when the
+		 * parent trigger is replaced by user.
+		 */
+		if (existing_isInternal && !isInternal && !in_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * can't replace constraint trigger.
+		 */
+		if (OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -728,15 +804,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -754,37 +821,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -910,12 +946,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		was_replaced = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -960,6 +1013,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (was_replaced)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d765..4b07617 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4313,6 +4313,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4321,7 +4323,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168..7dbaf67 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,21 +5211,22 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
@@ -5238,6 +5239,7 @@ CreateTrigStmt:
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
+					n->replace = false;
 					n->trigname = $4;
 					n->relation = $8;
 					n->funcname = $17;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff584f2..8361317 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2433,6 +2433,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2444,7 +2446,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c19aac9..b9fdee2 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3140,3 +3140,169 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index bf2e73a..5588655 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2348,3 +2348,162 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+insert into my_table (id) values (1);
+create or replace trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB();
+insert into my_table (id) values (2);
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test trigger can't be replaced without OR REPLACE clause
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  before insert on my_table
+  for each row execute procedure funcA(); -- should fail
+drop trigger my_trig on my_table;
+drop function funcA();
+drop function funcB();
+drop table my_table;
+
+-- test for detecting incompatible replacement of trigger
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
-- 
1.8.3.1

#41Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#40)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hello Osumi-san.

I have checked the v15 patch with regard to my v14 review comments.

On Wed, Nov 4, 2020 at 2:53 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

(1) COMMENT
File: NA
Maybe next time consider using format-patch to make the patch. Then you
can include a comment to give the background/motivation for this change.

OK. How about v15 ?

Yes, it is good now, apart from some typos in the first sentence:
"grammer" --> "grammar"
"exisiting" --> "existing"

===

(2) COMMENT
File: doc/src/sgml/ref/create_trigger.sgml
@@ -446,6 +454,13 @@ UPDATE OF
<replaceable>column_name1</replaceable>
[, <replaceable>column_name2</
Currently it says:
When replacing an existing trigger with CREATE OR REPLACE TRIGGER,
there are restrictions. You cannot replace constraint triggers. That means it is
impossible to replace a regular trigger with a constraint trigger and to replace
a constraint trigger with another constraint trigger.

--

Is that correct wording? I don't think so. Saying "to replace a regular trigger
with a constraint trigger" is NOT the same as "replace a constraint trigger".

I corrected my wording in create_trigger.sgml, which should cause less confusion
than v14. The reason why I changed the documents is described below.

Yes, OK. But it might be simpler still just to it like:
"CREATE OR REPLACE TRIGGER works only for replacing a regular (not
constraint) trigger with another regular trigger."

Maybe I am mistaken but I think the help and the code are no longer in sync
anymore. e.g. In previous versions of this patch you used to verify
replacement trigger kinds (regular/constraint) match. AFAIK you are not
doing that in the current code (but you should be). So although you say
"impossible to replace a regular trigger with a constraint trigger" I don't see
any code to check/enforce that ( ?? )
IMO when you simplified this v14 patch you may have removed some extra
trigger kind conditions that should not have been removed.

Also, the test code should have detected this problem, but I think the tests
have also been broken in v14. See later COMMENT (9).

Don't worry and those are not broken.

I changed some codes in gram.y to throw a syntax error when
OR REPLACE clause is used with CREATE CONSTRAINT TRIGGER.

In the previous discussion with Tsunakawa-San in this thread,
I judged that OR REPLACE clause is
not necessary for *CONSTRAINT* TRIGGER to achieve the purpose of this patch.
It is to support the database migration from Oracle to Postgres
by supporting the same syntax for trigger replacement. Here,
because the constraint trigger is unique to the latter,
I prohibited the usage of CREATE CONSTRAINT TRIGGER and OR REPLACE
clauses at the same time at the grammatical level.
Did you agree with this way of modification ?

To prohibit the combination OR REPLACE and CONSTRAINT clauses may seem
a little bit radical but I refer to an example of the combination to use
CREATE CONSTRAINT TRIGGER and AFTER clause.
When the timing of trigger is not AFTER for CONSTRAINT TRIGGER,
an syntax error is caused. I learnt and followed the way for
my modification from it.

OK, I understand now. In my v14 review I failed to notice that you did
it this way, which is why I thought a check was missing in the code.

I do think this is a bit subtle. Perhaps this should be asserted and
commented a bit more in the code to make it much clearer what you did.
For example:
----------
BEFORE
/*
* can't replace constraint trigger.
*/
if (OidIsValid(existing_constraint_oid))
AFTER
/*
* It is not allowed to replace with a constraint trigger.
* The OR REPLACE syntax is not available for constraint triggers (see gram.y).
*/
Assert(!stmt->isconstraint);

/*
* It is not allowed to replace an existing constraint trigger.
*/
if (OidIsValid(existing_constraint_oid))
----------

(9) COMMENT
File: src/test/regress/expected/triggers.out
+-- test for detecting incompatible replacement of trigger create table
+my_table (id integer); create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$ begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); create constraint trigger
+my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists

--

I think this test has been broken in v14. That last "create constraint trigger
my_trig" above can never be expected to work simply because you are not
specifying the "OR REPLACE" syntax.

As I described above, the grammatical error occurs to use
"CREATE OR REPLACE CONSTRAINT TRIGGER" in v14 (and v15 also).
At the time to write v14, I wanted to list up all imcompatible cases
even if some tests did *not* or can *not* contain "OR REPLACE" clause.
I think this way of change seemed broken to you.

Still now I think it's a good idea to cover such confusing cases,
so I didn't remove both failure tests in v15
(1) CREATE OR REPLACE TRIGGER creates a regular trigger and execute
CREATE CONSTRAINT TRIGGER, which should fail
(2) CREATE CONSTRAINT TRIGGER creates a constraint trigger and
execute CREATE OR REPLACE TRIGGER, which should fail
in order to show in such cases, the detection of error nicely works.
The results of tests are fine.

So in fact this is not properly testing for
incompatible types at all. It needs to say "create OR REPLACE constraint
trigger my_trig" to be testing what it claims to be testing.

I also think there is a missing check in the code - see COMMENT (2) - for
handling this scenario. But since this test case is broken you do not then
notice the code check is missing.

===

My inappropriate explanation especially in the create_trigger.sgml
made you think those are broken. But, as I said they are necessary still
to cover corner combination cases.

Yes, I agree that all the combinations should be present. That is why
I wrote the "create constraint trigger" should be written "create OR
REPLACE constraint trigger" because otherwise AFAIK there is no test
attempting to replace using a constraint trigger - you are only really
testing you cannot create a duplicate name trigger (but those tests
already existed)

In other words, IMO the "incompatible" tests should be like below (I
added comments to try make it more clear what are the combinations)
----------
create or replace trigger my_trig
after insert on my_table
for each row execute procedure funcA(); -- 1. create a regular trigger. OK
create or replace constraint trigger my_trig
after insert on my_table
for each row execute procedure funcB(); -- Test 1a. Replace regular
trigger with constraint trigger. Expect ERROR (bad syntax)
drop trigger my_trig on my_table;
create constraint trigger my_trig -- 2. create a constraint trigger. OK
after insert on my_table
for each row execute procedure funcA();
create or replace trigger my_trig
after insert on my_table
for each row execute procedure funcB(); -- Test 2a. Replace
constraint trigger with regular trigger. Expect ERROR (cannot replace
a constraint trigger)
create or replace constraint trigger my_trig
after insert on my_table
for each row execute procedure funcB(); -- Test 2b. Replace
constraint trigger with constraint trigger. Expect ERROR (bad syntax)
drop table my_table;
drop function funcA();
drop function funcB();
----------

Kind Regards,
Peter Smith.
Fujitsu Australia

#42osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#41)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi,

On Thursday, November 5, 2020 1:22 PM
Peter Smith <smithpb2250@gmail.com> wrote:

On Wed, Nov 4, 2020 at 2:53 PM osumi.takamichi@fujitsu.com
<osumi.takamichi@fujitsu.com> wrote:

(1) COMMENT
File: NA
Maybe next time consider using format-patch to make the patch. Then
you can include a comment to give the background/motivation for this

change.

OK. How about v15 ?

Yes, it is good now, apart from some typos in the first sentence:
"grammer" --> "grammar"
"exisiting" --> "existing"

Sorry for such minor mistakes. Fixed.

===

(2) COMMENT
File: doc/src/sgml/ref/create_trigger.sgml
@@ -446,6 +454,13 @@ UPDATE OF
<replaceable>column_name1</replaceable>
[, <replaceable>column_name2</
Currently it says:
When replacing an existing trigger with CREATE OR REPLACE TRIGGER,
there are restrictions. You cannot replace constraint triggers. That
means it is impossible to replace a regular trigger with a
constraint trigger and to replace a constraint trigger with another

constraint trigger.

--

Is that correct wording? I don't think so. Saying "to replace a
regular trigger with a constraint trigger" is NOT the same as "replace a

constraint trigger".

I corrected my wording in create_trigger.sgml, which should cause less
confusion than v14. The reason why I changed the documents is described

below.

Yes, OK. But it might be simpler still just to it like:
"CREATE OR REPLACE TRIGGER works only for replacing a regular (not
constraint) trigger with another regular trigger."

Yeah, this kind of supplementary words help user to understand the
exact usage of this feature. Thanks.

Maybe I am mistaken but I think the help and the code are no longer
in sync anymore. e.g. In previous versions of this patch you used to
verify replacement trigger kinds (regular/constraint) match. AFAIK
you are not doing that in the current code (but you should be). So
although you say "impossible to replace a regular trigger with a
constraint trigger" I don't see any code to check/enforce that ( ??
) IMO when you simplified this v14 patch you may have removed some
extra trigger kind conditions that should not have been removed.

Also, the test code should have detected this problem, but I think
the tests have also been broken in v14. See later COMMENT (9).

Don't worry and those are not broken.

I changed some codes in gram.y to throw a syntax error when OR REPLACE
clause is used with CREATE CONSTRAINT TRIGGER.

In the previous discussion with Tsunakawa-San in this thread, I judged
that OR REPLACE clause is not necessary for *CONSTRAINT* TRIGGER to
achieve the purpose of this patch.
It is to support the database migration from Oracle to Postgres by
supporting the same syntax for trigger replacement. Here, because the
constraint trigger is unique to the latter, I prohibited the usage of
CREATE CONSTRAINT TRIGGER and OR REPLACE clauses at the same

time at

the grammatical level.
Did you agree with this way of modification ?

To prohibit the combination OR REPLACE and CONSTRAINT clauses may

seem

a little bit radical but I refer to an example of the combination to
use CREATE CONSTRAINT TRIGGER and AFTER clause.
When the timing of trigger is not AFTER for CONSTRAINT TRIGGER, an
syntax error is caused. I learnt and followed the way for my
modification from it.

OK, I understand now. In my v14 review I failed to notice that you did it this
way, which is why I thought a check was missing in the code.

I do think this is a bit subtle. Perhaps this should be asserted and commented
a bit more in the code to make it much clearer what you did.
For example:
----------
BEFORE
/*
* can't replace constraint trigger.
*/
if (OidIsValid(existing_constraint_oid))
AFTER
/*
* It is not allowed to replace with a constraint trigger.
* The OR REPLACE syntax is not available for constraint triggers (see
gram.y).
*/
Assert(!stmt->isconstraint);

/*
* It is not allowed to replace an existing constraint trigger.
*/
if (OidIsValid(existing_constraint_oid))
----------

Agreed.
Note that this part of the latest patch v16 shows different indent of the comments
that you gave me in your previous reply but those came from the execution of pgindent.

(9) COMMENT
File: src/test/regress/expected/triggers.out
+-- test for detecting incompatible replacement of trigger create
+table my_table (id integer); create function funcA() returns
+trigger as $$ begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$ begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); create constraint trigger
+my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists

--

I think this test has been broken in v14. That last "create
constraint trigger my_trig" above can never be expected to work
simply because you are not specifying the "OR REPLACE" syntax.

As I described above, the grammatical error occurs to use "CREATE OR
REPLACE CONSTRAINT TRIGGER" in v14 (and v15 also).
At the time to write v14, I wanted to list up all imcompatible cases
even if some tests did *not* or can *not* contain "OR REPLACE" clause.
I think this way of change seemed broken to you.

Still now I think it's a good idea to cover such confusing cases, so I
didn't remove both failure tests in v15
(1) CREATE OR REPLACE TRIGGER creates a regular trigger and execute
CREATE CONSTRAINT TRIGGER, which should fail
(2) CREATE CONSTRAINT TRIGGER creates a constraint trigger and
execute CREATE OR REPLACE TRIGGER, which should fail in order to
show in such cases, the detection of error nicely works.
The results of tests are fine.

So in fact this is not properly testing for incompatible types at
all. It needs to say "create OR REPLACE constraint trigger my_trig"
to be testing what it claims to be testing.

I also think there is a missing check in the code - see COMMENT (2)
- for handling this scenario. But since this test case is broken you
do not then notice the code check is missing.

===

My inappropriate explanation especially in the create_trigger.sgml
made you think those are broken. But, as I said they are necessary
still to cover corner combination cases.

Yes, I agree that all the combinations should be present. That is why I wrote
the "create constraint trigger" should be written "create OR REPLACE
constraint trigger" because otherwise AFAIK there is no test attempting to
replace using a constraint trigger - you are only really testing you cannot
create a duplicate name trigger (but those tests already existed)

In other words, IMO the "incompatible" tests should be like below (I added
comments to try make it more clear what are the combinations)
----------
create or replace trigger my_trig
after insert on my_table
for each row execute procedure funcA(); -- 1. create a regular trigger. OK
create or replace constraint trigger my_trig after insert on my_table for
each row execute procedure funcB(); -- Test 1a. Replace regular trigger with
constraint trigger. Expect ERROR (bad syntax) drop trigger my_trig on
my_table; create constraint trigger my_trig -- 2. create a constraint trigger. OK
after insert on my_table for each row execute procedure funcA(); create or
replace trigger my_trig after insert on my_table for each row execute
procedure funcB(); -- Test 2a. Replace constraint trigger with regular trigger.
Expect ERROR (cannot replace a constraint trigger) create or replace
constraint trigger my_trig after insert on my_table for each row execute
procedure funcB(); -- Test 2b. Replace constraint trigger with constraint
trigger. Expect ERROR (bad syntax) drop table my_table; drop function
funcA(); drop function funcB();
----------

I understand that
I need to add 2 syntax error cases and
1 error case to replace constraint trigger at least. It makes sense.
At the same time, I supposed that the order of the tests
in v15 patch is somehow hard to read.
So, I decided to sort out those and take your new sets of tests there.
What I'd like to test there is not different, though.
Please have a look at the new patch.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v16.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v16.patchDownload
From ce767406a0c4a2404822cfc43bd62fb7ba2514b2 Mon Sep 17 00:00:00 2001
From: Osumi Takamichi <osumi.takamichi@fujitsu.com>
Date: Thu, 5 Nov 2020 12:54:08 +0000
Subject: [PATCH v16] Support CREATE OR REPLACE TRIGGER

This patch extends the grammar of CREATE TRIGGER to
accept OR REPLACE clause in order to replace an
existing regular trigger. The purpose of this patch
is to support migration from Oracle Database to Postgres
by giving compatible syntax for trigger replacement.
This allows user to replace a regular existing trigger
while it doesn't allow them to replace a constraint
existing trigger, which is unique to Postgres.

Author: Takamichi Osumi <osumi.takamichi@fujitsu.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Tsunakawa, Takayuki <tsunakawa.takay@fujitsu.com>
Discussion: https://www.postgresql.org/message-id/0DDF369B45A1B44B8A687ED43F06557C010BC362%40G01JPEXMBYT03
---
 doc/src/sgml/ref/create_trigger.sgml   |  21 +++-
 src/backend/commands/tablecmds.c       |   9 +-
 src/backend/commands/trigger.c         | 161 ++++++++++++++++++--------
 src/backend/nodes/copyfuncs.c          |   3 +-
 src/backend/nodes/equalfuncs.c         |   3 +-
 src/backend/parser/gram.y              |  24 ++--
 src/include/nodes/parsenodes.h         |   3 +-
 src/test/regress/expected/triggers.out | 202 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 195 +++++++++++++++++++++++++++++++
 9 files changed, 555 insertions(+), 66 deletions(-)

diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..399106a 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing regular trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing regular trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,13 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   <command>CREATE OR REPLACE TRIGGER</command> works only for replacing a regular
+   (not constraint) trigger with another regular trigger.
+   That means it is impossible to replace a regular trigger with a constraint trigger
+   and to replace a constraint trigger with another constraint trigger.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a29c14b..d883e58 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10518,6 +10518,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10538,7 +10540,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10567,6 +10568,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10575,7 +10578,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10623,6 +10625,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10631,7 +10635,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac16..01cfb74 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -168,15 +169,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		was_replaced = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		existing_isInternal = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -669,6 +673,85 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			existing_isInternal = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * An internal trigger cannot be replaced by another user defined
+		 * trigger. This should exclude the case that internal trigger is
+		 * child trigger of partition table and needs to be rewritten when the
+		 * parent trigger is replaced by user.
+		 */
+		if (existing_isInternal && !isInternal && !in_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * It is not allowed to replace with a constraint trigger. The OR
+		 * REPLACE syntax is not available for constraint triggers (see
+		 * gram.y).
+		 */
+		Assert(!stmt->isconstraint);
+
+		/*
+		 * It is not allowed to replace an existing constraint trigger.
+		 */
+		if (OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -728,15 +811,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -754,37 +828,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -910,12 +953,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		was_replaced = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -960,6 +1020,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (was_replaced)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d765..4b07617 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4313,6 +4313,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4321,7 +4323,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168..7dbaf67 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,21 +5211,22 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
@@ -5238,6 +5239,7 @@ CreateTrigStmt:
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
+					n->replace = false;
 					n->trigname = $4;
 					n->relation = $8;
 					n->funcname = $17;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff584f2..8361317 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2433,6 +2433,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2444,7 +2446,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c19aac9..dac7d27 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3140,3 +3140,205 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+-- regular trigger cannot be replaced by another one without OR REPLACE clause
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- regular trigger can be replaced by another one with OR REPLACE clause
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+-- regular trigger cannot be replaced by constraint trigger
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- create or replace constraint trigger is not correct grammatically
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- regular trigger cannot be replaced by another one without OR REPLACE clause
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- regular trigger can be replaced by another one with OR REPLACE clause
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+-- regular trigger cannot be replaced by constraint trigger
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- create or replace constraint trigger is not correct gramatically
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- constraint trigger cannot be replaced by regular trigger
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- constraint trigger cannot be replaced by regular trigger even with OR REPLACE clause
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+drop trigger my_trig on my_table;
+-- constraint trigger cannot be replaced by constraint trigger
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- create or replace constraint trigger is not gramatically correct.
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- cleanup
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index bf2e73a..96a03b6 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2348,3 +2348,198 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+
+-- regular trigger cannot be replaced by another one without OR REPLACE clause
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- regular trigger can be replaced by another one with OR REPLACE clause
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+drop trigger my_trig on my_table;
+
+-- regular trigger cannot be replaced by constraint trigger
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- create or replace constraint trigger is not correct grammatically
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- regular trigger cannot be replaced by another one without OR REPLACE clause
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- regular trigger can be replaced by another one with OR REPLACE clause
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+drop trigger my_trig on my_table;
+
+-- regular trigger cannot be replaced by constraint trigger
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- create or replace constraint trigger is not correct gramatically
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- constraint trigger cannot be replaced by regular trigger
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- constraint trigger cannot be replaced by regular trigger even with OR REPLACE clause
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- constraint trigger cannot be replaced by constraint trigger
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- create or replace constraint trigger is not gramatically correct.
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- cleanup
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
-- 
1.8.3.1

#43Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#42)
2 attachment(s)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hello Osumi-san.

I have checked again v16 patch w.r.t. to my previous comments.

(2) COMMENT
File: doc/src/sgml/ref/create_trigger.sgml
@@ -446,6 +454,13 @@ UPDATE OF
<replaceable>column_name1</replaceable>
[, <replaceable>column_name2</
Currently it says:
When replacing an existing trigger with CREATE OR REPLACE TRIGGER,
there are restrictions. You cannot replace constraint triggers. That
means it is impossible to replace a regular trigger with a
constraint trigger and to replace a constraint trigger with another

constraint trigger.

--

Is that correct wording? I don't think so. Saying "to replace a
regular trigger with a constraint trigger" is NOT the same as "replace a

constraint trigger".

I corrected my wording in create_trigger.sgml, which should cause less
confusion than v14. The reason why I changed the documents is described

below.

Yes, OK. But it might be simpler still just to it like:
"CREATE OR REPLACE TRIGGER works only for replacing a regular (not
constraint) trigger with another regular trigger."

Yeah, this kind of supplementary words help user to understand the
exact usage of this feature. Thanks.

Actually, I meant that after making that 1st sentence wording change,
I thought the 2nd sentence (i.e. "That means it is impossible...") is
no longer needed at all since it is just re-stating what the 1st
sentence already says.

But if you prefer to leave it worded how it is now that is ok too.

(9) COMMENT

(snip)

I understand that
I need to add 2 syntax error cases and
1 error case to replace constraint trigger at least. It makes sense.
At the same time, I supposed that the order of the tests
in v15 patch is somehow hard to read.
So, I decided to sort out those and take your new sets of tests there.
What I'd like to test there is not different, though.
Please have a look at the new patch.

Yes, the tests are generally OK, but unfortunately a few new problems
are introduced with the refactoring of the combination tests.

1) It looks like about 40 lines of test code are cut/paste 2 times by accident
2) Typo "gramatically" --> "grammatically"
3) Your last test described as "create or replace constraint trigger
is not gramatically correct." is not really doing what it is meant to
do. That test was supposed to be trying to replace an existing
CONSTRAINT trigger.

IMO if all the combination tests were consistently commented like my 8
examples below then risk of accidental mistakes is reduced.
e.g.
-- 1. Overwrite existing regular trigger with regular trigger (without
OR REPLACE)
-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
-- 3. Overwrite existing regular trigger with constraint trigger
(without OR REPLACE)
-- 4. Overwrite existing regular trigger with constraint trigger (with
OR REPLACE)
-- 5. Overwrite existing constraint trigger with regular trigger
(without OR REPLACE)
-- 6. Overwrite existing constraint trigger with regular trigger (with
OR REPLACE)
-- 7. Overwrite existing constraint trigger with constraint trigger
(without OR REPLACE)
-- 8. Overwrite existing constraint trigger with constraint trigger
(with OR REPLACE)

To avoid any confusion I have attached triggers.sql updated how I
think it should be. Please compare it to see what I mean. PSA.

I hope it helps.

===

Kind Regards,
Peter Smith.
Fujitsu Australia

Attachments:

triggers.sql.mineapplication/octet-stream; name=triggers.sql.mineDownload
triggers.sql.v16application/octet-stream; name=triggers.sql.v16Download
#44osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Peter Smith (#43)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hello

On Friday, November 6, 2020 2:25 PM Peter Smith <smithpb2250@gmail.com> wrote:

Yes, OK. But it might be simpler still just to it like:
"CREATE OR REPLACE TRIGGER works only for replacing a regular (not
constraint) trigger with another regular trigger."

Yeah, this kind of supplementary words help user to understand the
exact usage of this feature. Thanks.

Actually, I meant that after making that 1st sentence wording change, I
thought the 2nd sentence (i.e. "That means it is impossible...") is no longer
needed at all since it is just re-stating what the 1st sentence already says.

But if you prefer to leave it worded how it is now that is ok too.

The simpler, the better for sure ? I deleted that 2nd sentence.

(9) COMMENT

(snip)

I understand that
I need to add 2 syntax error cases and
1 error case to replace constraint trigger at least. It makes sense.
At the same time, I supposed that the order of the tests in v15 patch
is somehow hard to read.
So, I decided to sort out those and take your new sets of tests there.
What I'd like to test there is not different, though.
Please have a look at the new patch.

Yes, the tests are generally OK, but unfortunately a few new problems are
introduced with the refactoring of the combination tests.

1) It looks like about 40 lines of test code are cut/paste 2 times by accident

This was not a mistake. The cases of 40 lines are with OR REPLACE to define
each regular trigger that will be overwritten.
But, it doesn't make nothing probably so I deleted such cases.
Please forget that part.

2) Typo "gramatically" --> "grammatically"
3) Your last test described as "create or replace constraint trigger is not
gramatically correct." is not really doing what it is meant to do. That test was
supposed to be trying to replace an existing CONSTRAINT trigger.

Sigh. Yeah, those were not right. Fixed.

IMO if all the combination tests were consistently commented like my 8
examples below then risk of accidental mistakes is reduced.
e.g.
-- 1. Overwrite existing regular trigger with regular trigger (without OR
REPLACE)
-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
-- 3. Overwrite existing regular trigger with constraint trigger (without OR
REPLACE)
-- 4. Overwrite existing regular trigger with constraint trigger (with OR
REPLACE)
-- 5. Overwrite existing constraint trigger with regular trigger (without OR
REPLACE)
-- 6. Overwrite existing constraint trigger with regular trigger (with OR
REPLACE)
-- 7. Overwrite existing constraint trigger with constraint trigger (without OR
REPLACE)
-- 8. Overwrite existing constraint trigger with constraint trigger (with OR
REPLACE)

To avoid any confusion I have attached triggers.sql updated how I think it
should be. Please compare it to see what I mean. PSA.

I hope it helps.

I cannot thank you enough.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v17.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v17.patchDownload
From 11c2f111e52bfaca964897df07704cae9ccbf8cb Mon Sep 17 00:00:00 2001
From: Osumi Takamichi <osumi.takamichi@fujitsu.com>
Date: Fri, 6 Nov 2020 07:24:44 +0000
Subject: [PATCH v17] Support CREATE OR REPLACE TRIGGER

This patch extends the grammar of CREATE TRIGGER to
accept OR REPLACE clause in order to replace an
existing regular trigger. The purpose of this patch
is to support migration from Oracle Database to Postgres
by giving compatible syntax for trigger replacement.
This allows user to replace a regular existing trigger
while it doesn't allow them to replace a constraint
existing trigger, which is unique to Postgres.

Author: Takamichi Osumi <osumi.takamichi@fujitsu.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Tsunakawa, Takayuki <tsunakawa.takay@fujitsu.com>
Discussion: https://www.postgresql.org/message-id/0DDF369B45A1B44B8A687ED43F06557C010BC362%40G01JPEXMBYT03
---
 doc/src/sgml/ref/create_trigger.sgml   |  19 +++-
 src/backend/commands/tablecmds.c       |   9 +-
 src/backend/commands/trigger.c         | 161 +++++++++++++++++++++++----------
 src/backend/nodes/copyfuncs.c          |   3 +-
 src/backend/nodes/equalfuncs.c         |   3 +-
 src/backend/parser/gram.y              |  24 ++---
 src/include/nodes/parsenodes.h         |   3 +-
 src/test/regress/expected/triggers.out | 161 +++++++++++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 157 ++++++++++++++++++++++++++++++++
 9 files changed, 474 insertions(+), 66 deletions(-)

diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..69fb454 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing regular trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing regular trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,11 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   <command>CREATE OR REPLACE TRIGGER</command> works only for replacing a regular
+   (not constraint) trigger with another regular trigger.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a29c14b..d883e58 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10518,6 +10518,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10538,7 +10540,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10567,6 +10568,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10575,7 +10578,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10623,6 +10625,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10631,7 +10635,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac16..01cfb74 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -168,15 +169,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		was_replaced = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		existing_isInternal = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -669,6 +673,85 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			existing_isInternal = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * An internal trigger cannot be replaced by another user defined
+		 * trigger. This should exclude the case that internal trigger is
+		 * child trigger of partition table and needs to be rewritten when the
+		 * parent trigger is replaced by user.
+		 */
+		if (existing_isInternal && !isInternal && !in_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * It is not allowed to replace with a constraint trigger. The OR
+		 * REPLACE syntax is not available for constraint triggers (see
+		 * gram.y).
+		 */
+		Assert(!stmt->isconstraint);
+
+		/*
+		 * It is not allowed to replace an existing constraint trigger.
+		 */
+		if (OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -728,15 +811,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -754,37 +828,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -910,12 +953,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		was_replaced = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -960,6 +1020,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (was_replaced)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d765..4b07617 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4313,6 +4313,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4321,7 +4323,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b98..6e261db 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2020,6 +2020,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2028,7 +2030,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168..7dbaf67 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5211,21 +5211,22 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
@@ -5238,6 +5239,7 @@ CreateTrigStmt:
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
+					n->replace = false;
 					n->trigname = $4;
 					n->relation = $8;
 					n->funcname = $17;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff584f2..8361317 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2433,6 +2433,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2444,7 +2446,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index c19aac9..5dd42ea 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3140,3 +3140,164 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+-- 1. Overwrite existing regular trigger with regular trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+NOTICE:  hello from funcB
+drop trigger my_trig on my_table;
+-- 3. Overwrite existing regular trigger with constraint trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- 4. Overwrite existing regular trigger with constraint trigger (with OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- 5. Overwrite existing constraint trigger with regular trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- 6. Overwrite existing constraint trigger with regular trigger (with OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+drop trigger my_trig on my_table;
+-- 7. Overwrite existing constraint trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+drop trigger my_trig on my_table;
+-- 8. Overwrite existing constraint trigger with constraint trigger (with OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- cleanup
+drop table my_table;
+drop function funcA();
+drop function funcB();
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index bf2e73a..b02c58a 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2348,3 +2348,160 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+
+-- 1. Overwrite existing regular trigger with regular trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+drop trigger my_trig on my_table;
+
+-- 3. Overwrite existing regular trigger with constraint trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 4. Overwrite existing regular trigger with constraint trigger (with OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 5. Overwrite existing constraint trigger with regular trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 6. Overwrite existing constraint trigger with regular trigger (with OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 7. Overwrite existing constraint trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 8. Overwrite existing constraint trigger with constraint trigger (with OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- cleanup
+drop table my_table;
+drop function funcA();
+drop function funcB();
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+truncate parted_trig;
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
-- 
1.8.3.1

#45Peter Smith
smithpb2250@gmail.com
In reply to: osumi.takamichi@fujitsu.com (#44)
Re: extension patch of CREATE OR REPLACE TRIGGER

Hello Osumi-san.

I have checked the latest v17 patch w.r.t. to my previous comments.

The v17 patch applies cleanly.

make check is successful.

The regenerated docs look OK.

I have no further review comments, so have flagged this v17 as "ready
for committer" - https://commitfest.postgresql.org/30/2307/

---

Kind Regards,
Peter Smith.
Fujitsu Australia

#46Dilip Kumar
dilipbalaut@gmail.com
In reply to: Peter Smith (#45)
Re: extension patch of CREATE OR REPLACE TRIGGER

On Sat, Nov 7, 2020 at 10:00 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hello Osumi-san.

I have checked the latest v17 patch w.r.t. to my previous comments.

The v17 patch applies cleanly.

make check is successful.

The regenerated docs look OK.

I have no further review comments, so have flagged this v17 as "ready
for committer" - https://commitfest.postgresql.org/30/2307/

The patch looks fine to me however I feel that in the test case there
are a lot of duplicate statement which can be reduced
e.g.
+-- 1. Overwrite existing regular trigger with regular trigger
(without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+insert into my_table values (1);
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+drop trigger my_trig on my_table;

In this test, test 1 failed because it tried to change the trigger
function without OR REPLACE, which is fine but now test 2 can continue
from there, I mean we don't need to drop the trigger at end of the
test1 and then test2 can try it with OR REPLACE syntax. This way we
can reduce the extra statement execution which is not necessary.

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

#47osumi.takamichi@fujitsu.com
osumi.takamichi@fujitsu.com
In reply to: Dilip Kumar (#46)
1 attachment(s)
RE: extension patch of CREATE OR REPLACE TRIGGER

Hi,

On Saturday, Nov 7, 2020 2:06 PM Dilip Kumar <dilipbalaut@gmail.com> wrote:

The patch looks fine to me however I feel that in the test case there are a lot
of duplicate statement which can be reduced e.g.
+-- 1. Overwrite existing regular trigger with regular trigger
(without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail drop trigger
+my_trig on my_table;
+
+-- 2. Overwrite existing regular trigger with regular trigger (with OR
+REPLACE) create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); insert into my_table values
+(1); create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK insert into my_table
+values (1); drop trigger my_trig on my_table;

In this test, test 1 failed because it tried to change the trigger function without
OR REPLACE, which is fine but now test 2 can continue from there, I mean we
don't need to drop the trigger at end of the
test1 and then test2 can try it with OR REPLACE syntax. This way we can
reduce the extra statement execution which is not necessary.

OK. That makes sense.

Attached the revised version.
The tests in this patch should not include redundancy.
I checked the tests of trigger replacement for partition tables as well.

Here, I did not and will not delete the comments with numbering from 1 to 8 so that
other developers can check if the all cases are listed up or not easily.

Best,
Takamichi Osumi

Attachments:

CREATE_OR_REPLACE_TRIGGER_v18.patchapplication/octet-stream; name=CREATE_OR_REPLACE_TRIGGER_v18.patchDownload
From 603b13e33a498416fdece8561d752a0fac6a5a46 Mon Sep 17 00:00:00 2001
From: Osumi Takamichi <osumi.takamichi@fujitsu.com>
Date: Mon, 9 Nov 2020 02:52:37 +0000
Subject: [PATCH v18] Support CREATE OR REPLACE TRIGGER

This patch extends the grammar of CREATE TRIGGER to
accept OR REPLACE clause in order to replace an
existing regular trigger. The purpose of this patch
is to support migration from Oracle Database to Postgres
by giving compatible syntax for trigger replacement.
This allows user to replace a regular existing trigger
while it doesn't allow them to replace a constraint
existing trigger, which is unique to Postgres.

Author: Takamichi Osumi <osumi.takamichi@fujitsu.com>
Reviewed-by: Peter Smith <smithpb2250@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Tsunakawa, Takayuki <tsunakawa.takay@fujitsu.com>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>
Discussion: https://www.postgresql.org/message-id/0DDF369B45A1B44B8A687ED43F06557C010BC362%40G01JPEXMBYT03
---
 doc/src/sgml/ref/create_trigger.sgml   |  19 +++-
 src/backend/commands/tablecmds.c       |   9 +-
 src/backend/commands/trigger.c         | 161 +++++++++++++++++++++++----------
 src/backend/nodes/copyfuncs.c          |   3 +-
 src/backend/nodes/equalfuncs.c         |   3 +-
 src/backend/parser/gram.y              |  24 ++---
 src/include/nodes/parsenodes.h         |   3 +-
 src/test/regress/expected/triggers.out | 124 +++++++++++++++++++++++++
 src/test/regress/sql/triggers.sql      | 120 ++++++++++++++++++++++++
 9 files changed, 400 insertions(+), 66 deletions(-)

diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml
index 60346e1..69fb454 100644
--- a/doc/src/sgml/ref/create_trigger.sgml
+++ b/doc/src/sgml/ref/create_trigger.sgml
@@ -26,7 +26,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
+CREATE [ OR REPLACE ] [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="parameter">event</replaceable> [ OR ... ] }
     ON <replaceable class="parameter">table_name</replaceable>
     [ FROM <replaceable class="parameter">referenced_table_name</replaceable> ]
     [ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
@@ -48,14 +48,22 @@ CREATE [ CONSTRAINT ] TRIGGER <replaceable class="parameter">name</replaceable>
   <title>Description</title>
 
   <para>
-   <command>CREATE TRIGGER</command> creates a new trigger.  The
-   trigger will be associated with the specified table, view, or foreign table
+   <command>CREATE TRIGGER</command> creates a new trigger.
+   <command>CREATE OR REPLACE TRIGGER</command> will either create a
+   new trigger, or replace an existing regular trigger. The trigger will be
+   associated with the specified table, view, or foreign table
    and will execute the specified
    function <replaceable class="parameter">function_name</replaceable> when
    certain operations are performed on that table.
   </para>
 
   <para>
+   To replace the current definition of an existing regular trigger, use
+   <command>CREATE OR REPLACE TRIGGER</command>, by specifying the same
+   trigger's name and the corresponding table's name where the trigger belongs.
+  </para>
+
+  <para>
    The trigger can be specified to fire before the
    operation is attempted on a row (before constraints are checked and
    the <command>INSERT</command>, <command>UPDATE</command>, or
@@ -446,6 +454,11 @@ UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</
   </para>
 
   <para>
+   <command>CREATE OR REPLACE TRIGGER</command> works only for replacing a regular
+   (not constraint) trigger with another regular trigger.
+  </para>
+
+  <para>
    A column-specific trigger (one defined using the <literal>UPDATE OF
    <replaceable>column_name</replaceable></literal> syntax) will fire when any
    of its columns are listed as targets in the <command>UPDATE</command>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e3cfaf8..95b1887 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10550,6 +10550,8 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10570,7 +10572,6 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = NULL;
@@ -10599,6 +10600,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * DELETE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10607,7 +10610,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_del_action)
 	{
@@ -10655,6 +10657,8 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	 * UPDATE action on the referenced table.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
 	fk_trigger->trigname = "RI_ConstraintTrigger_a";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
@@ -10663,7 +10667,6 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->columns = NIL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->whenClause = NULL;
-	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
 	switch (fkconstraint->fk_upd_action)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac16..01cfb74 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -168,15 +169,14 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *qual;
 	Datum		values[Natts_pg_trigger];
 	bool		nulls[Natts_pg_trigger];
+	bool		replaces[Natts_pg_trigger];
 	Relation	rel;
 	AclResult	aclresult;
 	Relation	tgrel;
-	SysScanDesc tgscan;
-	ScanKeyData key;
 	Relation	pgrel;
 	HeapTuple	tuple;
 	Oid			funcrettype;
-	Oid			trigoid;
+	Oid			trigoid = InvalidOid;
 	char		internaltrigname[NAMEDATALEN];
 	char	   *trigname;
 	Oid			constrrelid = InvalidOid;
@@ -185,6 +185,10 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	char	   *oldtablename = NULL;
 	char	   *newtablename = NULL;
 	bool		partition_recurse;
+	bool		was_replaced = false;
+	Oid			existing_constraint_oid = InvalidOid;
+	bool		trigger_exists = false;
+	bool		existing_isInternal = false;
 
 	if (OidIsValid(relOid))
 		rel = table_open(relOid, ShareRowExclusiveLock);
@@ -669,6 +673,85 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		whenRtable = NIL;
 	}
 
+	/* Check if there is a pre-existing trigger of the same name */
+	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
+	if (!isInternal)
+	{
+		ScanKeyData skeys[2];
+		SysScanDesc my_desc;
+
+		ScanKeyInit(&skeys[0],
+					Anum_pg_trigger_tgrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(RelationGetRelid(rel)));
+
+		ScanKeyInit(&skeys[1],
+					Anum_pg_trigger_tgname,
+					BTEqualStrategyNumber, F_NAMEEQ,
+					CStringGetDatum(stmt->trigname));
+
+		my_desc = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
+									 NULL, 2, skeys);
+
+		/* There should be at most one matching tuple */
+		if (HeapTupleIsValid(tuple = systable_getnext(my_desc)))
+		{
+			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+			tuple = heap_copytuple(tuple);
+			trigoid = pg_trigger->oid;
+			existing_constraint_oid = pg_trigger->tgconstraint;
+			existing_isInternal = pg_trigger->tgisinternal;
+			trigger_exists = true;
+		}
+		systable_endscan(my_desc);
+	}
+
+	/* Generate the trigger's oid because there was no same name trigger. */
+	if (!trigger_exists)
+		trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
+									 Anum_pg_trigger_oid);
+	else
+	{
+		/*
+		 * without OR REPLACE clause, can't override the trigger with the same
+		 * name.
+		 */
+		if (!stmt->replace)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" already exists",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * An internal trigger cannot be replaced by another user defined
+		 * trigger. This should exclude the case that internal trigger is
+		 * child trigger of partition table and needs to be rewritten when the
+		 * parent trigger is replaced by user.
+		 */
+		if (existing_isInternal && !isInternal && !in_partition)
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is an internal trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+
+		/*
+		 * It is not allowed to replace with a constraint trigger. The OR
+		 * REPLACE syntax is not available for constraint triggers (see
+		 * gram.y).
+		 */
+		Assert(!stmt->isconstraint);
+
+		/*
+		 * It is not allowed to replace an existing constraint trigger.
+		 */
+		if (OidIsValid(existing_constraint_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_OBJECT),
+					 errmsg("trigger \"%s\" for relation \"%s\" is a constraint trigger",
+							stmt->trigname, RelationGetRelationName(rel))));
+	}
+
 	/*
 	 * Find and validate the trigger function.
 	 */
@@ -728,15 +811,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Generate the trigger's OID now, so that we can use it in the name if
-	 * needed.
-	 */
-	tgrel = table_open(TriggerRelationId, RowExclusiveLock);
-
-	trigoid = GetNewOidWithIndex(tgrel, TriggerOidIndexId,
-								 Anum_pg_trigger_oid);
-
-	/*
 	 * If trigger is internally generated, modify the provided trigger name to
 	 * ensure uniqueness by appending the trigger OID.  (Callers will usually
 	 * supply a simple constant trigger name in these cases.)
@@ -754,37 +828,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	}
 
 	/*
-	 * Scan pg_trigger for existing triggers on relation.  We do this only to
-	 * give a nice error message if there's already a trigger of the same
-	 * name.  (The unique index on tgrelid/tgname would complain anyway.) We
-	 * can skip this for internally generated triggers, since the name
-	 * modification above should be sufficient.
-	 *
-	 * NOTE that this is cool only because we have ShareRowExclusiveLock on
-	 * the relation, so the trigger set won't be changing underneath us.
-	 */
-	if (!isInternal)
-	{
-		ScanKeyInit(&key,
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(RelationGetRelid(rel)));
-		tgscan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true,
-									NULL, 1, &key);
-		while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-		{
-			Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-
-			if (namestrcmp(&(pg_trigger->tgname), trigname) == 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_DUPLICATE_OBJECT),
-						 errmsg("trigger \"%s\" for relation \"%s\" already exists",
-								trigname, RelationGetRelationName(rel))));
-		}
-		systable_endscan(tgscan);
-	}
-
-	/*
 	 * Build the new pg_trigger tuple.
 	 *
 	 * When we're creating a trigger in a partition, we mark it as internal,
@@ -910,12 +953,29 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		nulls[Anum_pg_trigger_tgnewtable - 1] = true;
 
-	tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
-
 	/*
 	 * Insert tuple into pg_trigger.
 	 */
-	CatalogTupleInsert(tgrel, tuple);
+	if (!trigger_exists)
+	{
+		tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+		CatalogTupleInsert(tgrel, tuple);
+	}
+	else
+	{
+		TupleDesc	tupDesc;
+		HeapTuple	newtup;
+
+		tupDesc = RelationGetDescr(tgrel);
+		memset(replaces, true, sizeof(replaces));
+		replaces[Anum_pg_trigger_oid - 1] = false;
+		replaces[Anum_pg_trigger_tgrelid - 1] = false;
+		replaces[Anum_pg_trigger_tgname - 1] = false;
+		newtup = heap_modify_tuple(tuple, tupDesc, values, nulls, replaces);
+		CatalogTupleUpdate(tgrel, &tuple->t_self, newtup);
+		was_replaced = true;
+		heap_freetuple(newtup);
+	}
 
 	heap_freetuple(tuple);
 	table_close(tgrel, RowExclusiveLock);
@@ -960,6 +1020,15 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	myself.objectId = trigoid;
 	myself.objectSubId = 0;
 
+	/*
+	 * In order to replace trigger, trigger should not be dependent on old
+	 * referenced objects. Remove the old dependencies and then register new
+	 * ones. In this case, while the old referenced object gets dropped,
+	 * trigger will remain in the database.
+	 */
+	if (was_replaced)
+		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3031c52..df6034d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4305,6 +4305,8 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	CreateTrigStmt *newnode = makeNode(CreateTrigStmt);
 
 	COPY_STRING_FIELD(trigname);
+	COPY_SCALAR_FIELD(replace);
+	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(funcname);
 	COPY_NODE_FIELD(args);
@@ -4313,7 +4315,6 @@ _copyCreateTrigStmt(const CreateTrigStmt *from)
 	COPY_SCALAR_FIELD(events);
 	COPY_NODE_FIELD(columns);
 	COPY_NODE_FIELD(whenClause);
-	COPY_SCALAR_FIELD(isconstraint);
 	COPY_NODE_FIELD(transitionRels);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9aa8537..98740bb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2012,6 +2012,8 @@ static bool
 _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 {
 	COMPARE_STRING_FIELD(trigname);
+	COMPARE_SCALAR_FIELD(replace);
+	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(funcname);
 	COMPARE_NODE_FIELD(args);
@@ -2020,7 +2022,6 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
 	COMPARE_SCALAR_FIELD(events);
 	COMPARE_NODE_FIELD(columns);
 	COMPARE_NODE_FIELD(whenClause);
-	COMPARE_SCALAR_FIELD(isconstraint);
 	COMPARE_NODE_FIELD(transitionRels);
 	COMPARE_SCALAR_FIELD(deferrable);
 	COMPARE_SCALAR_FIELD(initdeferred);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 95e2568..4d37e3c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -5218,21 +5218,22 @@ am_type:
  *****************************************************************************/
 
 CreateTrigStmt:
-			CREATE TRIGGER name TriggerActionTime TriggerEvents ON
+			CREATE opt_or_replace TRIGGER name TriggerActionTime TriggerEvents ON
 			qualified_name TriggerReferencing TriggerForSpec TriggerWhen
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
-					n->trigname = $3;
-					n->relation = $7;
-					n->funcname = $13;
-					n->args = $15;
-					n->row = $9;
-					n->timing = $4;
-					n->events = intVal(linitial($5));
-					n->columns = (List *) lsecond($5);
-					n->whenClause = $10;
-					n->transitionRels = $8;
+					n->replace = $2;
+					n->trigname = $4;
+					n->relation = $8;
+					n->funcname = $14;
+					n->args = $16;
+					n->row = $10;
+					n->timing = $5;
+					n->events = intVal(linitial($6));
+					n->columns = (List *) lsecond($6);
+					n->whenClause = $11;
+					n->transitionRels = $9;
 					n->isconstraint  = false;
 					n->deferrable	 = false;
 					n->initdeferred  = false;
@@ -5245,6 +5246,7 @@ CreateTrigStmt:
 			EXECUTE FUNCTION_or_PROCEDURE func_name '(' TriggerFuncArgs ')'
 				{
 					CreateTrigStmt *n = makeNode(CreateTrigStmt);
+					n->replace = false;
 					n->trigname = $4;
 					n->relation = $8;
 					n->funcname = $17;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7ef9b0e..a2dcdef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2425,6 +2425,8 @@ typedef struct CreateAmStmt
 typedef struct CreateTrigStmt
 {
 	NodeTag		type;
+	bool		replace;		/* replace trigger if already exists */
+	bool		isconstraint;	/* This is a constraint trigger */
 	char	   *trigname;		/* TRIGGER's name */
 	RangeVar   *relation;		/* relation trigger is on */
 	List	   *funcname;		/* qual. name of function to call */
@@ -2436,7 +2438,6 @@ typedef struct CreateTrigStmt
 	int16		events;			/* "OR" of INSERT/UPDATE/DELETE/TRUNCATE */
 	List	   *columns;		/* column names, or NIL for all columns */
 	Node	   *whenClause;		/* qual expression, or NULL if none */
-	bool		isconstraint;	/* This is a constraint trigger */
 	/* explicitly named transition data */
 	List	   *transitionRels; /* TriggerTransition nodes, or NIL if none */
 	/* The remaining fields are only used for constraint triggers */
diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out
index 027494b..859883e 100644
--- a/src/test/regress/expected/triggers.out
+++ b/src/test/regress/expected/triggers.out
@@ -3140,3 +3140,127 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+-- 1. Overwrite existing regular trigger with regular trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
+insert into my_table values (1);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+NOTICE:  hello from funcB
+-- 3. Overwrite existing regular trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+-- 4. Overwrite existing regular trigger with constraint trigger (with OR REPLACE)
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+drop trigger my_trig on my_table;
+-- 5. Overwrite existing constraint trigger with regular trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+-- 6. Overwrite existing constraint trigger with regular trigger (with OR REPLACE)
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" is a constraint trigger
+-- 7. Overwrite existing constraint trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "my_table" already exists
+-- 8. Overwrite existing constraint trigger with constraint trigger (with OR REPLACE)
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+ERROR:  syntax error at or near "constraint"
+LINE 1: create or replace constraint trigger my_trig
+                          ^
+-- cleanup
+drop trigger my_trig on my_table;
+drop table my_table;
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+ERROR:  trigger "my_trig" for relation "parted_trig_1" is an internal trigger
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+drop trigger my_trig on parted_trig;
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcA
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+NOTICE:  hello from funcB
+drop trigger my_trig on parted_trig;
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql
index 212b4f1..37c5eeb 100644
--- a/src/test/regress/sql/triggers.sql
+++ b/src/test/regress/sql/triggers.sql
@@ -2348,3 +2348,123 @@ create trigger aft_row after insert or update on trigger_parted
 create table trigger_parted_p1 partition of trigger_parted for values in (1)
   partition by list (a);
 create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1);
+
+--
+-- Test case for CREATE OR REPLACE TRIGGER
+--
+create table my_table (id integer);
+create function funcA() returns trigger as $$
+begin
+  raise notice 'hello from funcA';
+  return null;
+end; $$ language plpgsql;
+create function funcB() returns trigger as $$
+begin
+  raise notice 'hello from funcB';
+  return null;
+end; $$ language plpgsql;
+
+-- consider combination tests from 2 grammatical points
+-- (1) the trigger is a regular trigger or constraint trigger
+-- (2) OR REPLACE clause is used or not
+
+-- 1. Overwrite existing regular trigger with regular trigger (without OR REPLACE)
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+
+-- 2. Overwrite existing regular trigger with regular trigger (with OR REPLACE)
+insert into my_table values (1);
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- OK
+insert into my_table values (1);
+
+-- 3. Overwrite existing regular trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+
+-- 4. Overwrite existing regular trigger with constraint trigger (with OR REPLACE)
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA(); -- should fail
+drop trigger my_trig on my_table;
+
+-- 5. Overwrite existing constraint trigger with regular trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcA();
+create trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+
+-- 6. Overwrite existing constraint trigger with regular trigger (with OR REPLACE)
+create or replace trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+
+-- 7. Overwrite existing constraint trigger with constraint trigger (without OR REPLACE)
+create constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+
+-- 8. Overwrite existing constraint trigger with constraint trigger (with OR REPLACE)
+create or replace constraint trigger my_trig
+  after insert on my_table
+  for each row execute procedure funcB(); -- should fail
+
+-- cleanup
+drop trigger my_trig on my_table;
+drop table my_table;
+
+-- test CREATE OR REPLACE TRIGGER on partition table
+create table parted_trig (a int) partition by range (a);
+create table parted_trig_1 partition of parted_trig
+       for values from (0) to (1000) partition by range (a);
+create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100);
+create table parted_trig_2 partition of parted_trig for values from (1000) to (2000);
+create table default_parted_trig partition of parted_trig default;
+
+-- test that trigger can be replaced by another one
+-- at the same level of partition table
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that child trigger cannot be replaced directly
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcB(); -- should fail
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- test that trigger is overwritten by another one
+-- defined by upper level
+create or replace trigger my_trig
+  after insert on parted_trig_1
+  for each row execute procedure funcA();
+insert into parted_trig (a) values (50);
+create or replace trigger my_trig
+  after insert on parted_trig
+  for each row execute procedure funcB();
+insert into parted_trig (a) values (50);
+drop trigger my_trig on parted_trig;
+
+-- cleanup
+drop table parted_trig;
+drop function funcA();
+drop function funcB();
-- 
1.8.3.1

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: osumi.takamichi@fujitsu.com (#47)
Re: extension patch of CREATE OR REPLACE TRIGGER

"osumi.takamichi@fujitsu.com" <osumi.takamichi@fujitsu.com> writes:

[ CREATE_OR_REPLACE_TRIGGER_v18.patch ]

Pushed with some mostly-minor cleanup.

(I know this has been a very long slog. Congratulations for
seeing it through.)

regards, tom lane