diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 7d57f97..5f76262 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -29,6 +29,9 @@
 #include "executor/executor.h"
 #include "executor/spi.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
@@ -38,6 +41,7 @@
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -61,13 +65,13 @@ static bool transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static uint64 refresh_matview_datafill(DestReceiver *dest, Query *query,
-						 const char *queryString);
+						 const char *queryString, Node *filter_clause);
 
 static char *make_temptable_name_n(char *tempname, int n);
 static void mv_GenerateOper(StringInfo buf, Oid opoid);
 
 static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
-					   int save_sec_context);
+					   int save_sec_context, const char *filter_clause_str);
 static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
 
 static void OpenMatViewIncrementalMaintenance(void);
@@ -113,6 +117,64 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
 	CommandCounterIncrement();
 }
 
+/*
+ * Given a subquery, and a filter clause, create the equivalent of
+ * SELECT * FROM <subquery> WHERE <filter clause>
+ */
+static Query *
+mv_refresh_build_subquery_with_filter (Query *subquery,
+								Node *filter_clause)
+{
+	Query *scan_query = makeNode (Query);
+	
+	scan_query->commandType = 1;
+	scan_query->querySource = QSRC_ORIGINAL;
+	scan_query->resultRelation = 0;
+	scan_query->hasAggs = false;
+	scan_query->hasWindowFuncs = false;
+	scan_query->hasTargetSRFs = false;
+	scan_query->hasSubLinks = false;
+	scan_query->hasDistinctOn = false;
+	scan_query->hasRecursive = false;
+	scan_query->hasModifyingCTE = false;
+	scan_query->hasForUpdate = false;
+	scan_query->hasRowSecurity = false; // FIXME: is this correct, or should we inherit?
+	
+	scan_query->cteList = NIL;
+	
+	/* Create a dummy ParseState for addRangeTableEntryForSubquery */
+	ParseState *pstate = make_parsestate(NULL);
+	
+	RangeTblEntry *rte = addRangeTableEntryForSubquery (pstate, subquery, makeAlias ("tab", NIL), false, false);
+	scan_query->rtable = list_make1 (rte);
+	
+	RangeTblRef *rtr = makeNode (RangeTblRef);
+	rtr->rtindex = 1;
+	
+	scan_query->jointree = makeNode (FromExpr);
+	scan_query->jointree->fromlist = list_make1 (rtr);
+	
+	if (filter_clause != NULL)
+	{
+		scan_query->jointree->quals = filter_clause;
+	}
+	
+	scan_query->targetList = expandRelAttrs (pstate, rte, 1, 0, -1);
+	
+	scan_query->override = OVERRIDING_NOT_SET;
+	scan_query->returningList = NIL;
+	scan_query->rowMarks = NIL;
+	scan_query->setOperations = NULL;
+	scan_query->constraintDeps = NIL;
+	scan_query->withCheckOptions = NIL;
+	
+	scan_query->stmt_location = -1;
+	scan_query->stmt_len = -1;
+	
+	return scan_query;
+}
+
+
 /*
  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
  *
@@ -260,6 +322,43 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
 	}
 
+	/* Construct a filter clause, if one was supplied. */
+	char *filter_clause_str = NULL;
+	Node *filter_clause = NULL;
+	if (stmt->whereClause != NULL)
+	{
+		if (!concurrent)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot partially refresh materialized view \"%s\" that cannot be refreshed concurrently",
+							quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													   RelationGetRelationName(matviewRel))),
+					 errhint("Create a unique index with no WHERE clause on one or more columns of the materialized view.")));
+		
+		ParseState *pstate = make_parsestate(NULL);
+
+		RangeTblEntry *rte = addRangeTableEntryForRelation(pstate, matviewRel,
+														   NULL, false, false);
+		addRTEtoQuery(pstate, rte, false, true, true);
+		
+		filter_clause = transformWhereClause(pstate,
+										  copyObject(stmt->whereClause),
+										  EXPR_KIND_WHERE,
+										  "WHERE");
+
+		if (contain_volatile_functions(filter_clause))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot partially refresh materialized view \"%s\" using volatile expresions in the WHERE clause",
+							quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)),
+													   RelationGetRelationName(matviewRel))),
+					 errhint("refresh the materialized view without a WHERE clause, or adjust the expressions or function definitions.")));
+
+		filter_clause_str = deparse_expression (filter_clause,
+												deparse_context_for ("tab", matviewOid),
+												true, false);
+	}
+	
 	/*
 	 * The stored query was rewritten at the time of the MV definition, but
 	 * has not been scribbled on by the planner.
@@ -324,7 +423,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
 	/* Generate the data, if wanted. */
 	if (!stmt->skipData)
-		processed = refresh_matview_datafill(dest, dataQuery, queryString);
+		processed = refresh_matview_datafill(dest, dataQuery, queryString, filter_clause);
 
 	/* Make the matview match the newly generated data. */
 	if (concurrent)
@@ -334,7 +433,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 		PG_TRY();
 		{
 			refresh_by_match_merge(matviewOid, OIDNewHeap, relowner,
-								   save_sec_context);
+								   save_sec_context, filter_clause_str);
 		}
 		PG_CATCH();
 		{
@@ -382,7 +481,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static uint64
 refresh_matview_datafill(DestReceiver *dest, Query *query,
-						 const char *queryString)
+						 const char *queryString, Node *filter_clause)
 {
 	List	   *rewritten;
 	PlannedStmt *plan;
@@ -402,6 +501,12 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
+	
+	/* Construct as subquery and apply filter, if filter is provided. */
+	if (filter_clause != NULL)
+	{
+		query = mv_refresh_build_subquery_with_filter (query, filter_clause);
+	}
 
 	/* Plan the query which will generate data for the refresh. */
 	plan = pg_plan_query(query, 0, NULL);
@@ -610,7 +715,7 @@ mv_GenerateOper(StringInfo buf, Oid opoid)
  */
 static void
 refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
-					   int save_sec_context)
+					   int save_sec_context, const char *filter_clause_str)
 {
 	StringInfoData querybuf;
 	Relation	matviewRel;
@@ -689,8 +794,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	appendStringInfo(&querybuf,
 					 "CREATE TEMP TABLE %s AS "
 					 "SELECT mv.ctid AS tid, newdata "
-					 "FROM %s mv FULL JOIN %s newdata ON (",
-					 diffname, matviewname, tempname);
+					 "FROM ("
+					 "  SELECT tab.ctid, tab.*, row (tab.*) tab_row FROM %s tab %s %s" // FIXME: rename column vars to avoid conflit with
+					 ") mv FULL JOIN %s newdata ON (",
+					 diffname, matviewname,
+					 filter_clause_str != NULL ? "WHERE" : "", filter_clause_str,
+					 tempname);
 
 	/*
 	 * Get the list of index OIDs for the table from the relcache, and look up
@@ -772,7 +881,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 	Assert(foundUniqueIndex);
 
 	appendStringInfoString(&querybuf,
-						   " AND newdata OPERATOR(pg_catalog.*=) mv) "
+						   " AND newdata OPERATOR(pg_catalog.*=) mv.tab_row) "
 						   "WHERE newdata IS NULL OR mv IS NULL "
 						   "ORDER BY tid");
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4d67070..c4a5114 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3803,6 +3803,7 @@ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from)
 	COPY_SCALAR_FIELD(concurrent);
 	COPY_SCALAR_FIELD(skipData);
 	COPY_NODE_FIELD(relation);
+	COPY_NODE_FIELD(whereClause);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03..e9ab121 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1696,6 +1696,7 @@ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt *
 	COMPARE_SCALAR_FIELD(concurrent);
 	COMPARE_SCALAR_FIELD(skipData);
 	COMPARE_NODE_FIELD(relation);
+	COMPARE_NODE_FIELD(whereClause);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d0de99..358dfd4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3972,12 +3972,13 @@ OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
  *****************************************************************************/
 
 RefreshMatViewStmt:
-			REFRESH MATERIALIZED VIEW opt_concurrently qualified_name opt_with_data
+			REFRESH MATERIALIZED VIEW opt_concurrently qualified_name opt_with_data where_clause
 				{
 					RefreshMatViewStmt *n = makeNode(RefreshMatViewStmt);
 					n->concurrent = $4;
 					n->relation = $5;
 					n->skipData = !($6);
+					n->whereClause = $7;
 					$$ = (Node *) n;
 				}
 		;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ef6753e..3b7091b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3150,6 +3150,7 @@ typedef struct RefreshMatViewStmt
 	NodeTag		type;
 	bool		concurrent;		/* allow concurrent access? */
 	bool		skipData;		/* true for WITH NO DATA */
+	Node	   *whereClause;	/* WHERE qualification */
 	RangeVar   *relation;		/* relation to insert into */
 } RefreshMatViewStmt;
 
