diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index a171ebabf8..8712a7f61b 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -565,7 +565,7 @@ make_temptable_name_n(char *tempname, int n)
  * the old record (if matched) and the ROW from the new table as a single
  * column of complex record type (if matched).
  *
- * Once we have the diff table, we perform set-based DELETE and INSERT
+ * Once we have the diff table, we perform set-based DELETE, UPDATE, and INSERT
  * operations against the materialized view, and discard both temporary
  * tables.
  *
@@ -590,6 +590,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	bool		foundUniqueIndex;
 	List	   *indexoidlist;
 	ListCell   *indexoidscan;
+	AttrNumber	relattno;
 	int16		relnatts;
 	Oid		   *opUsedForQual;
 
@@ -779,8 +780,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	Assert(foundUniqueIndex);
 
 	appendStringInfoString(&querybuf,
-						   " AND newdata OPERATOR(pg_catalog.*=) mv) "
+						   ") "
 						   "WHERE newdata IS NULL OR mv IS NULL "
+						   "OR newdata OPERATOR(pg_catalog.*<>) mv "
 						   "ORDER BY tid");
 
 	/* Create the temporary "diff" table. */
@@ -803,7 +805,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 
 	OpenMatViewIncrementalMaintenance();
 
-	/* Deletes must come before inserts; do them first. */
+	/* We do deletes first. */
 	resetStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					 "DELETE FROM %s mv WHERE ctid OPERATOR(pg_catalog.=) ANY "
@@ -814,7 +816,38 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	if (SPI_exec(querybuf.data, 0) != SPI_OK_DELETE)
 		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
 
-	/* Inserts go last. */
+	/* Then we do updates. */
+	resetStringInfo(&querybuf);
+	appendStringInfo(&querybuf, "UPDATE %s mv SET (", matviewname);
+
+	for (relattno = 1; relattno <= relnatts; relattno++)
+	{
+		Form_pg_attribute attribute = TupleDescAttr(tupdesc, relattno - 1);
+		char	   *attributeName = NameStr(attribute->attname);
+
+		/* Ignore dropped */
+		if (attribute->attisdropped)
+			continue;
+
+		if (relattno == 1)
+		{
+			appendStringInfo(&querybuf, "%s", quote_identifier(attributeName));
+		}
+		else
+		{
+			appendStringInfo(&querybuf, ", %s", quote_identifier(attributeName));
+		}
+	}
+
+	appendStringInfo(&querybuf,
+					 ") = ROW((diff.newdata).*) FROM %s diff "
+					 "WHERE diff.tid IS NOT NULL AND diff.newdata IS NOT NULL "
+					 "AND mv.ctid OPERATOR(pg_catalog.=) diff.tid",
+					 diffname);
+	if (SPI_exec(querybuf.data, 0) != SPI_OK_UPDATE)
+		elog(ERROR, "SPI_exec failed: %s", querybuf.data);
+
+	/* Inserts and updates go last. */
 	resetStringInfo(&querybuf);
 	appendStringInfo(&querybuf,
 					 "INSERT INTO %s SELECT (diff.newdata).* "
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fb0de60a45..8597def50a 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -208,6 +208,16 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 							RelationGetRelationName(rel)),
 					 errdetail("Tables cannot have INSTEAD OF triggers.")));
 	}
+	else if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+	{
+		/* Materialized views can have only AFTER triggers */
+		if (stmt->timing != TRIGGER_TYPE_AFTER)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("\"%s\" is a materialized view",
+							RelationGetRelationName(rel)),
+					 errdetail("Materialized views can have only AFTER triggers.")));
+	}
 	else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 	{
 		/* Partitioned tables can't have INSTEAD OF triggers */
@@ -307,7 +317,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or view",
+				 errmsg("\"%s\" is not a table, view, or materialized view",
 						RelationGetRelationName(rel))));
 
 	if (!allowSystemTableMods && IsSystemRelation(rel))
@@ -1513,11 +1523,12 @@ RemoveTriggerById(Oid trigOid)
 
 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
 		rel->rd_rel->relkind != RELKIND_VIEW &&
+		rel->rd_rel->relkind != RELKIND_MATVIEW &&
 		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
 		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table, view, or foreign table",
+				 errmsg("\"%s\" is not a table, view, materialized view, or foreign table",
 						RelationGetRelationName(rel))));
 
 	if (!allowSystemTableMods && IsSystemRelation(rel))
@@ -1619,11 +1630,12 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
 
 	/* only tables and views can have triggers */
 	if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
+		form->relkind != RELKIND_MATVIEW &&
 		form->relkind != RELKIND_FOREIGN_TABLE &&
 		form->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table, view, or foreign table",
+				 errmsg("\"%s\" is not a table, view, materialized view, or foreign table",
 						rv->relname)));
 
 	/* you must own the table to rename one of its triggers */
