*** a/contrib/oid2name/oid2name.c
--- b/contrib/oid2name/oid2name.c
***************
*** 494,500 **** sql_exec_dumpalltables(PGconn *conn, struct options * opts)
  		   "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
  			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),"
  			 "	pg_catalog.pg_tablespace t "
! 			 "WHERE relkind IN ('r'%s%s) AND "
  			 "	%s"
  			 "		t.oid = CASE"
  			 "			WHEN reltablespace <> 0 THEN reltablespace"
--- 494,500 ----
  		   "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
  			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),"
  			 "	pg_catalog.pg_tablespace t "
! 			 "WHERE relkind IN ('r', 'm'%s%s) AND "
  			 "	%s"
  			 "		t.oid = CASE"
  			 "			WHEN reltablespace <> 0 THEN reltablespace"
***************
*** 565,571 **** sql_exec_searchtables(PGconn *conn, struct options * opts)
  		 "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \n"
  			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n"
  			 "	pg_catalog.pg_tablespace t \n"
! 			 "WHERE relkind IN ('r', 'i', 'S', 't') AND \n"
  			 "		t.oid = CASE\n"
  			 "			WHEN reltablespace <> 0 THEN reltablespace\n"
  			 "			ELSE dattablespace\n"
--- 565,571 ----
  		 "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace \n"
  			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n"
  			 "	pg_catalog.pg_tablespace t \n"
! 			 "WHERE relkind IN ('r', 'm', 'i', 'S', 't') AND \n"
  			 "		t.oid = CASE\n"
  			 "			WHEN reltablespace <> 0 THEN reltablespace\n"
  			 "			ELSE dattablespace\n"
*** a/contrib/pg_upgrade/info.c
--- b/contrib/pg_upgrade/info.c
***************
*** 275,281 **** get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
  			 "CREATE TEMPORARY TABLE info_rels (reloid) AS SELECT c.oid "
  			 "FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
  			 "	   ON c.relnamespace = n.oid "
! 			 "WHERE relkind IN ('r', 'i'%s) AND "
  	/* exclude possible orphaned temp tables */
  			 "  ((n.nspname !~ '^pg_temp_' AND "
  			 "    n.nspname !~ '^pg_toast_temp_' AND "
--- 275,281 ----
  			 "CREATE TEMPORARY TABLE info_rels (reloid) AS SELECT c.oid "
  			 "FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
  			 "	   ON c.relnamespace = n.oid "
! 			 "WHERE relkind IN ('r', 'm', 'i'%s) AND "
  	/* exclude possible orphaned temp tables */
  			 "  ((n.nspname !~ '^pg_temp_' AND "
  			 "    n.nspname !~ '^pg_toast_temp_' AND "
*** a/contrib/pg_upgrade/pg_upgrade.c
--- b/contrib/pg_upgrade/pg_upgrade.c
***************
*** 425,432 **** set_frozenxids(void)
  		PQclear(executeQueryOrDie(conn,
  								  "UPDATE	pg_catalog.pg_class "
  								  "SET	relfrozenxid = '%u' "
! 		/* only heap and TOAST are vacuumed */
! 								  "WHERE	relkind IN ('r', 't')",
  								  old_cluster.controldata.chkpnt_nxtxid));
  		PQfinish(conn);
  
--- 425,432 ----
  		PQclear(executeQueryOrDie(conn,
  								  "UPDATE	pg_catalog.pg_class "
  								  "SET	relfrozenxid = '%u' "
! 		/* only heap, materialized view, and TOAST are vacuumed */
! 								  "WHERE	relkind IN ('r', 'm', 't')",
  								  old_cluster.controldata.chkpnt_nxtxid));
  		PQfinish(conn);
  
*** a/contrib/pg_upgrade/version_old_8_3.c
--- b/contrib/pg_upgrade/version_old_8_3.c
***************
*** 145,151 **** old_8_3_check_for_tsquery_usage(ClusterInfo *cluster)
  								"FROM	pg_catalog.pg_class c, "
  								"		pg_catalog.pg_namespace n, "
  								"		pg_catalog.pg_attribute a "
! 								"WHERE	c.relkind = 'r' AND "
  								"		c.oid = a.attrelid AND "
  								"		NOT a.attisdropped AND "
  								"		a.atttypid = 'pg_catalog.tsquery'::pg_catalog.regtype AND "
--- 145,151 ----
  								"FROM	pg_catalog.pg_class c, "
  								"		pg_catalog.pg_namespace n, "
  								"		pg_catalog.pg_attribute a "
! 								"WHERE	c.relkind in ('r', 'm') AND "
  								"		c.oid = a.attrelid AND "
  								"		NOT a.attisdropped AND "
  								"		a.atttypid = 'pg_catalog.tsquery'::pg_catalog.regtype AND "
***************
*** 323,329 **** old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode)
  								"FROM	pg_catalog.pg_class c, "
  								"		pg_catalog.pg_namespace n, "
  								"		pg_catalog.pg_attribute a "
! 								"WHERE	c.relkind = 'r' AND "
  								"		c.oid = a.attrelid AND "
  								"		NOT a.attisdropped AND "
  								"		a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND "
--- 323,329 ----
  								"FROM	pg_catalog.pg_class c, "
  								"		pg_catalog.pg_namespace n, "
  								"		pg_catalog.pg_attribute a "
! 								"WHERE	c.relkind in ('r', 'm') AND "
  								"		c.oid = a.attrelid AND "
  								"		NOT a.attisdropped AND "
  								"		a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND "
***************
*** 343,349 **** old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode)
  								"FROM	pg_catalog.pg_class c, "		\
  								"		pg_catalog.pg_namespace n, "	\
  								"		pg_catalog.pg_attribute a "		\
! 								"WHERE	c.relkind = 'r' AND "			\
  								"		c.oid = a.attrelid AND "		\
  								"		NOT a.attisdropped AND "		\
  								"		a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND " \
--- 343,349 ----
  								"FROM	pg_catalog.pg_class c, "		\
  								"		pg_catalog.pg_namespace n, "	\
  								"		pg_catalog.pg_attribute a "		\
! 								"WHERE	c.relkind in ('r', 'm') AND "	\
  								"		c.oid = a.attrelid AND "		\
  								"		NOT a.attisdropped AND "		\
  								"		a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND " \
*** a/contrib/pgstattuple/pgstattuple.c
--- b/contrib/pgstattuple/pgstattuple.c
***************
*** 216,221 **** pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
--- 216,222 ----
  	switch (rel->rd_rel->relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_TOASTVALUE:
  		case RELKIND_SEQUENCE:
  			return pgstat_heap(rel, fcinfo);
*** a/contrib/sepgsql/dml.c
--- b/contrib/sepgsql/dml.c
***************
*** 191,196 **** check_relation_privileges(Oid relOid,
--- 191,197 ----
  	switch (relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  			result = sepgsql_avc_check_perms(&object,
  											 SEPG_CLASS_DB_TABLE,
  											 required,
***************
*** 226,232 **** check_relation_privileges(Oid relOid,
  	/*
  	 * Only columns owned by relations shall be checked
  	 */
! 	if (relkind != RELKIND_RELATION)
  		return true;
  
  	/*
--- 227,233 ----
  	/*
  	 * Only columns owned by relations shall be checked
  	 */
! 	if (relkind != RELKIND_RELATION && relkind != RELKIND_MATVIEW)
  		return true;
  
  	/*
*** a/contrib/sepgsql/label.c
--- b/contrib/sepgsql/label.c
***************
*** 764,769 **** exec_object_restorecon(struct selabel_handle * sehnd, Oid catalogId)
--- 764,771 ----
  					objtype = SELABEL_DB_SEQUENCE;
  				else if (relForm->relkind == RELKIND_VIEW)
  					objtype = SELABEL_DB_VIEW;
+ 				else if (relForm->relkind == RELKIND_MATVIEW)
+ 					objtype = SELABEL_DB_TABLE;
  				else
  					continue;	/* no need to assign security label */
  
***************
*** 782,788 **** exec_object_restorecon(struct selabel_handle * sehnd, Oid catalogId)
  			case AttributeRelationId:
  				attForm = (Form_pg_attribute) GETSTRUCT(tuple);
  
! 				if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION)
  					continue;	/* no need to assign security label */
  
  				objtype = SELABEL_DB_COLUMN;
--- 784,791 ----
  			case AttributeRelationId:
  				attForm = (Form_pg_attribute) GETSTRUCT(tuple);
  
! 				if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION &&
! 					get_rel_relkind(attForm->attrelid) != RELKIND_MATVIEW)
  					continue;	/* no need to assign security label */
  
  				objtype = SELABEL_DB_COLUMN;
*** a/contrib/sepgsql/relation.c
--- b/contrib/sepgsql/relation.c
***************
*** 54,61 **** sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
  	Form_pg_attribute attForm;
  
  	/*
! 	 * Only attributes within regular relation have individual security
! 	 * labels.
  	 */
  	if (get_rel_relkind(relOid) != RELKIND_RELATION)
  		return;
--- 54,61 ----
  	Form_pg_attribute attForm;
  
  	/*
! 	 * Only attributes within regular relation can have ALTER to add columns
! 	 * with individual security labels.
  	 */
  	if (get_rel_relkind(relOid) != RELKIND_RELATION)
  		return;
***************
*** 159,165 **** sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
  	ObjectAddress object;
  	char	   *audit_name;
  
! 	if (get_rel_relkind(relOid) != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot set security label on non-regular columns")));
--- 159,166 ----
  	ObjectAddress object;
  	char	   *audit_name;
  
! 	if (get_rel_relkind(relOid) != RELKIND_RELATION &&
! 		get_rel_relkind(relOid) != RELKIND_MATVIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot set security label on non-regular columns")));
***************
*** 252,257 **** sepgsql_relation_post_create(Oid relOid)
--- 253,259 ----
  	switch (classForm->relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  			tclass = SEPG_CLASS_DB_TABLE;
  			tclass_text = "table";
  			break;
***************
*** 301,310 **** sepgsql_relation_post_create(Oid relOid)
  	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
  
  	/*
! 	 * We also assigns a default security label on columns of the new regular
! 	 * tables.
  	 */
! 	if (classForm->relkind == RELKIND_RELATION)
  	{
  		Relation	arel;
  		ScanKeyData akey;
--- 303,313 ----
  	SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
  
  	/*
! 	 * We also assign a default security label on columns of new regular
! 	 * tables and materialized views.
  	 */
! 	if (classForm->relkind == RELKIND_RELATION ||
! 		classForm->relkind == RELKIND_MATVIEW)
  	{
  		Relation	arel;
  		ScanKeyData akey;
***************
*** 378,383 **** sepgsql_relation_drop(Oid relOid)
--- 381,387 ----
  	switch (relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  			tclass = SEPG_CLASS_DB_TABLE;
  			break;
  		case RELKIND_SEQUENCE:
***************
*** 489,499 **** sepgsql_relation_relabel(Oid relOid, const char *seclabel)
  		tclass = SEPG_CLASS_DB_SEQUENCE;
  	else if (relkind == RELKIND_VIEW)
  		tclass = SEPG_CLASS_DB_VIEW;
  	else
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot set security labels on relations except "
! 						"for tables, sequences or views")));
  
  	object.classId = RelationRelationId;
  	object.objectId = relOid;
--- 493,505 ----
  		tclass = SEPG_CLASS_DB_SEQUENCE;
  	else if (relkind == RELKIND_VIEW)
  		tclass = SEPG_CLASS_DB_VIEW;
+ 	else if (relkind == RELKIND_MATVIEW)
+ 		tclass = SEPG_CLASS_DB_TABLE;
  	else
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot set security labels on relations except "
! 						"for tables, sequences, views, or materialized views")));
  
  	object.classId = RelationRelationId;
  	object.objectId = relOid;
***************
*** 536,541 **** sepgsql_relation_setattr(Oid relOid)
--- 542,548 ----
  	switch (get_rel_relkind(relOid))
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  			tclass = SEPG_CLASS_DB_TABLE;
  			break;
  		case RELKIND_SEQUENCE:
*** a/contrib/sepgsql/sepgsql.h
--- b/contrib/sepgsql/sepgsql.h
***************
*** 32,37 ****
--- 32,39 ----
  
  /*
   * Internally used code of object classes
+  *
+  * NOTE: Materialized views are treated as tables for now.
   */
  #define SEPG_CLASS_PROCESS			0
  #define SEPG_CLASS_FILE				1
*** a/contrib/vacuumlo/vacuumlo.c
--- b/contrib/vacuumlo/vacuumlo.c
***************
*** 209,215 **** vacuumlo(const char *database, const struct _param * param)
  	strcat(buf, "      AND a.atttypid = t.oid ");
  	strcat(buf, "      AND c.relnamespace = s.oid ");
  	strcat(buf, "      AND t.typname in ('oid', 'lo') ");
! 	strcat(buf, "      AND c.relkind = 'r'");
  	strcat(buf, "      AND s.nspname !~ '^pg_'");
  	res = PQexec(conn, buf);
  	if (PQresultStatus(res) != PGRES_TUPLES_OK)
--- 209,215 ----
  	strcat(buf, "      AND a.atttypid = t.oid ");
  	strcat(buf, "      AND c.relnamespace = s.oid ");
  	strcat(buf, "      AND t.typname in ('oid', 'lo') ");
! 	strcat(buf, "      AND c.relkind in ('r', 'm')");
  	strcat(buf, "      AND s.nspname !~ '^pg_'");
  	res = PQexec(conn, buf);
  	if (PQresultStatus(res) != PGRES_TUPLES_OK)
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1555,1562 ****
     The catalog <structname>pg_class</structname> catalogs tables and most
     everything else that has columns or is otherwise similar to a
     table.  This includes indexes (but see also
!    <structname>pg_index</structname>), sequences, views, composite types,
!    and TOAST tables; see <structfield>relkind</>.
     Below, when we mean all of these
     kinds of objects we speak of <quote>relations</quote>.  Not all
     columns are meaningful for all relation types.
--- 1555,1562 ----
     The catalog <structname>pg_class</structname> catalogs tables and most
     everything else that has columns or is otherwise similar to a
     table.  This includes indexes (but see also
!    <structname>pg_index</structname>), sequences, views, materialized
!    views, composite types, and TOAST tables; see <structfield>relkind</>.
     Below, when we mean all of these
     kinds of objects we speak of <quote>relations</quote>.  Not all
     columns are meaningful for all relation types.
***************
*** 1740,1747 ****
        <entry></entry>
        <entry>
         <literal>r</> = ordinary table, <literal>i</> = index,
!        <literal>S</> = sequence, <literal>v</> = view, <literal>c</> =
!        composite type, <literal>t</> = TOAST table,
         <literal>f</> = foreign table
        </entry>
       </row>
--- 1740,1748 ----
        <entry></entry>
        <entry>
         <literal>r</> = ordinary table, <literal>i</> = index,
!        <literal>S</> = sequence, <literal>v</> = view,
!        <literal>m</> = materialized view,
!        <literal>c</> = composite type, <literal>t</> = TOAST table,
         <literal>f</> = foreign table
        </entry>
       </row>
***************
*** 1814,1819 ****
--- 1815,1830 ----
       </row>
  
       <row>
+       <entry><structfield>relisvalid</structfield></entry>
+       <entry><type>boolean</type></entry>
+       <entry></entry>
+       <entry>
+        Relation is valid and can be scanned.  Currently, this is used only for
+        materialized views; for other relation types, it will always be true.
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>relfrozenxid</structfield></entry>
        <entry><type>xid</type></entry>
        <entry></entry>
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 21,26 **** Complete list of usable sgml source files in this directory.
--- 21,27 ----
  <!ENTITY alterIndex         SYSTEM "alter_index.sgml">
  <!ENTITY alterLanguage      SYSTEM "alter_language.sgml">
  <!ENTITY alterLargeObject   SYSTEM "alter_large_object.sgml">
+ <!ENTITY alterMaterializedView SYSTEM "alter_materialized_view.sgml">
  <!ENTITY alterOperator      SYSTEM "alter_operator.sgml">
  <!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
  <!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
***************
*** 62,67 **** Complete list of usable sgml source files in this directory.
--- 63,69 ----
  <!ENTITY createGroup        SYSTEM "create_group.sgml">
  <!ENTITY createIndex        SYSTEM "create_index.sgml">
  <!ENTITY createLanguage     SYSTEM "create_language.sgml">
+ <!ENTITY createMaterializedView SYSTEM "create_materialized_view.sgml">
  <!ENTITY createOperator     SYSTEM "create_operator.sgml">
  <!ENTITY createOperatorClass SYSTEM "create_opclass.sgml">
  <!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
***************
*** 101,106 **** Complete list of usable sgml source files in this directory.
--- 103,109 ----
  <!ENTITY dropGroup          SYSTEM "drop_group.sgml">
  <!ENTITY dropIndex          SYSTEM "drop_index.sgml">
  <!ENTITY dropLanguage       SYSTEM "drop_language.sgml">
+ <!ENTITY dropMaterializedView SYSTEM "drop_materialized_view.sgml">
  <!ENTITY dropOperator       SYSTEM "drop_operator.sgml">
  <!ENTITY dropOperatorClass  SYSTEM "drop_opclass.sgml">
  <!ENTITY dropOperatorFamily  SYSTEM "drop_opfamily.sgml">
***************
*** 129,134 **** Complete list of usable sgml source files in this directory.
--- 132,138 ----
  <!ENTITY insert             SYSTEM "insert.sgml">
  <!ENTITY listen             SYSTEM "listen.sgml">
  <!ENTITY load               SYSTEM "load.sgml">
+ <!ENTITY loadMaterializedView SYSTEM "load_materialized_view.sgml">
  <!ENTITY lock               SYSTEM "lock.sgml">
  <!ENTITY move               SYSTEM "move.sgml">
  <!ENTITY notify             SYSTEM "notify.sgml">
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 39,44 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
--- 39,45 ----
    FOREIGN DATA WRAPPER <replaceable class="PARAMETER">object_name</replaceable> |
    FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> |
    FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
+   MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
    OPERATOR <replaceable class="PARAMETER">operator_name</replaceable> (<replaceable class="PARAMETER">left_type</replaceable>, <replaceable class="PARAMETER">right_type</replaceable>) |
    OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
    OPERATOR FAMILY <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
*** /dev/null
--- b/doc/src/sgml/ref/alter_materialized_view.sgml
***************
*** 0 ****
--- 1,152 ----
+ <!--
+ doc/src/sgml/ref/alter_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+ 
+ <refentry id="SQL-ALTERMATERIALIZEDVIEW">
+  <refmeta>
+   <refentrytitle>ALTER MATERIALIZED VIEW</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>ALTER MATERIALIZED VIEW</refname>
+   <refpurpose>change the definition of a materialized view</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-alterview">
+   <primary>ALTER MATERIALIZED VIEW</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">view_option_name</replaceable> [= <replaceable class="parameter">view_option_value</replaceable>] [, ... ] )
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">view_option_name</replaceable> [, ... ] )
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1>
+   <title>Description</title>
+ 
+   <para>
+    <command>ALTER MATERIALIZED VIEW</command> changes various auxiliary
+    properties of a materialized view.
+   </para>
+ 
+   <para>
+    You must own the materialized view to use <command>ALTER MATERIALIZED
+    VIEW</>.  To change a materailized view's schema, you must also have
+    <literal>CREATE</> privilege on the new schema.
+    To alter the owner, you must also be a direct or indirect member of the new
+    owning role, and that role must have <literal>CREATE</literal> privilege on
+    the materialized view's schema.  (These restrictions enforce that altering
+    the owner doesn't do anything you couldn't do by dropping and recreating the
+    materialized view.  However, a superuser can alter ownership of any view
+    anyway.)
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><replaceable class="parameter">name</replaceable></term>
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of an existing materialized view.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the materialized view does not exist. A notice
+       is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">new_owner</replaceable></term>
+     <listitem>
+      <para>
+       The user name of the new owner of the materialized view.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">new_name</replaceable></term>
+     <listitem>
+      <para>
+       The new name for the materialized view.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">new_schema</replaceable></term>
+     <listitem>
+      <para>
+       The new schema for the materialized view.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">view_option_name</replaceable></term>
+     <listitem>
+      <para>
+       The name of a materialized view option to be set or reset.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="parameter">view_option_name</replaceable></term>
+     <listitem>
+      <para>
+       The new value for a view option.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Examples</title>
+ 
+   <para>
+    To rename the materialized view <literal>foo</literal> to
+    <literal>bar</literal>:
+ <programlisting>
+ ALTER MATERIALIZED VIEW foo RENAME TO bar;
+ </programlisting></para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Compatibility</title>
+ 
+   <para>
+    <command>ALTER MATERIALIZED VIEW</command> is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>See Also</title>
+ 
+   <simplelist type="inline">
+    <member><xref linkend="sql-creatematerializedview"></member>
+    <member><xref linkend="sql-dropmaterializedview"></member>
+    <member><xref linkend="sql-loadmaterializedview"></member>
+   </simplelist>
+  </refsect1>
+ </refentry>
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 38,43 **** COMMENT ON
--- 38,44 ----
    FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
    INDEX <replaceable class="PARAMETER">object_name</replaceable> |
    LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> |
+   MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
    OPERATOR <replaceable class="PARAMETER">operator_name</replaceable> (<replaceable class="PARAMETER">left_type</replaceable>, <replaceable class="PARAMETER">right_type</replaceable>) |
    OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
    OPERATOR FAMILY <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
***************
*** 279,284 **** COMMENT ON FUNCTION my_function (timestamp) IS 'Returns Roman Numeral';
--- 280,286 ----
  COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee ID';
  COMMENT ON LANGUAGE plpython IS 'Python support for stored procedures';
  COMMENT ON LARGE OBJECT 346344 IS 'Planning document';
+ COMMENT ON MATERIALIZED VIEW my_matview IS 'Summary of order history';
  COMMENT ON OPERATOR ^ (text, text) IS 'Performs intersection of two texts';
  COMMENT ON OPERATOR - (NONE, integer) IS 'Unary minus';
  COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees';
*** /dev/null
--- b/doc/src/sgml/ref/create_materialized_view.sgml
***************
*** 0 ****
--- 1,244 ----
+ <!--
+ doc/src/sgml/ref/create_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+ 
+ <refentry id="SQL-CREATEMATERIALIZEDVIEW">
+  <refmeta>
+   <refentrytitle>CREATE MATERIALIZED VIEW</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>CREATE MATERIALIZED VIEW</refname>
+   <refpurpose>define a new materialized view</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-creatematerializedview">
+   <primary>CREATE MATERIALIZED VIEW</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] MATERIALIZED VIEW <replaceable>table_name</replaceable>
+     [ (<replaceable>column_name</replaceable> [, ...] ) ]
+     [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+     [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+     [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+     AS <replaceable>query</replaceable>
+     [ WITH [ NO ] DATA ]
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1>
+   <title>Description</title>
+ 
+   <para>
+    <command>CREATE MATERIALIZED VIEW</command> defines a materialized view of
+    a query.  The query is executed and used to populate the view at the time
+    the command is issued (unless <command>WITH NO DATA</> is used) and may be
+    refreshed later using <command>LOAD MATERIALIZED VIEW</command>.
+   </para>
+ 
+   <para>
+    <command>CREATE MATERIALIZED VIEW</command> is similar to
+    <command>CREATE TABLE AS</>, except that it also remembers the query used
+    to initialize the view, so that it can be refreshed later upon demand.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><literal>GLOBAL</literal> or <literal>LOCAL</literal></term>
+     <listitem>
+      <para>
+       Ignored for compatibility.  Use of these keywords is deprecated;
+       refer to <xref linkend="sql-createtable"> for details.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><literal>TEMPORARY</> or <literal>TEMP</></term>
+     <listitem>
+      <para>
+       If specified, the materialized view will be temporary.
+       Refer to <xref linkend="sql-createtable"> for details.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>UNLOGGED</></term>
+     <listitem>
+      <para>
+       If specified, the materialized view will be unlogged.
+       Refer to <xref linkend="sql-createtable"> for details.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable>table_name</replaceable></term>
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the materialized view to be
+       created.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable>column_name</replaceable></term>
+     <listitem>
+      <para>
+       The name of a column in the new materialized view.  If column names are
+       not provided, they are taken from the output column names of the query.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )</literal></term>
+     <listitem>
+      <para>
+       This clause specifies optional storage parameters for the new
+       materialized view; see <xref linkend="sql-createtable-storage-parameters"
+       endterm="sql-createtable-storage-parameters-title"> for more
+       information.  The <literal>WITH</> clause
+       can also include <literal>OIDS=TRUE</> (or just <literal>OIDS</>)
+       to specify that rows of the new table
+       should have OIDs (object identifiers) assigned to them, or
+       <literal>OIDS=FALSE</> to specify that the rows should not have OIDs.
+       See <xref linkend="sql-createtable"> for more information.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>WITH OIDS</></term>
+     <term><literal>WITHOUT OIDS</></term>
+     <listitem>
+      <para>
+       These are obsolescent syntaxes equivalent to <literal>WITH (OIDS)</>
+       and <literal>WITH (OIDS=FALSE)</>, respectively.  If you wish to give
+       both an <literal>OIDS</> setting and storage parameters, you must use
+       the <literal>WITH ( ... )</> syntax; see above.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>ON COMMIT</literal></term>
+     <listitem>
+      <para>
+       The behavior of temporary materialized views at the end of a transaction
+       block can be controlled using <literal>ON COMMIT</literal>.
+       The three options are:
+ 
+       <variablelist>
+        <varlistentry>
+         <term><literal>PRESERVE ROWS</literal></term>
+         <listitem>
+          <para>
+           No special action is taken at the ends of transactions.
+           This is the default behavior.
+          </para>
+         </listitem>
+        </varlistentry>
+ 
+        <varlistentry>
+         <term><literal>DELETE ROWS</literal></term>
+         <listitem>
+          <para>
+           All rows in the temporary materialized view will be deleted at the
+           end of each transaction block.  Essentially, an automatic <xref
+           linkend="sql-truncate"> is done
+           at each commit.
+          </para>
+         </listitem>
+        </varlistentry>
+ 
+        <varlistentry>
+         <term><literal>DROP</literal></term>
+         <listitem>
+          <para>
+           The temporary materialized view will be dropped at the end of the
+           current transaction block.
+          </para>
+         </listitem>
+        </varlistentry>
+       </variablelist></para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable></literal></term>
+     <listitem>
+      <para>
+       The <replaceable class="PARAMETER">tablespace_name</replaceable> is the name
+       of the tablespace in which the new materialized view is to be created.
+       If not specified,
+       <xref linkend="guc-default-tablespace"> is consulted, or
+       <xref linkend="guc-temp-tablespaces"> if the materialized view is
+       temporary.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable>query</replaceable></term>
+     <listitem>
+      <para>
+       A <xref linkend="sql-select">, <link
+       linkend="sql-table">TABLE</link>, or <xref linkend="sql-values">
+       command, or an <xref linkend="sql-execute"> command that runs a
+       prepared <command>SELECT</>, <command>TABLE</>, or
+       <command>VALUES</> query.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>WITH [ NO ] DATA</></term>
+     <listitem>
+      <para>
+       This clause specifies whether or not the materialized view should be
+       populated at creation time.  If not, the materialized view will be
+       flagged as invalid and cannot be queried until <command>LOAD
+       MATERIALIZED VIEW</> is used.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Compatibility</title>
+ 
+   <para>
+    <command>CREATE MATERIALIZED VIEW</command> is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>See Also</title>
+ 
+   <simplelist type="inline">
+    <member><xref linkend="sql-altermaterializedview"></member>
+    <member><xref linkend="sql-createtableas"></member>
+    <member><xref linkend="sql-createview"></member>
+    <member><xref linkend="sql-dropmaterializedview"></member>
+    <member><xref linkend="sql-loadmaterializedview"></member>
+   </simplelist>
+  </refsect1>
+ 
+ </refentry>
*** a/doc/src/sgml/ref/create_table_as.sgml
--- b/doc/src/sgml/ref/create_table_as.sgml
***************
*** 340,345 **** CREATE TEMP TABLE films_recent WITH (OIDS) ON COMMIT DROP AS
--- 340,346 ----
    <title>See Also</title>
  
    <simplelist type="inline">
+    <member><xref linkend="sql-creatematerializedview"></member>
     <member><xref linkend="sql-createtable"></member>
     <member><xref linkend="sql-execute"></member>
     <member><xref linkend="sql-select"></member>
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
***************
*** 256,261 **** CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c
--- 256,262 ----
    <title>See Also</title>
  
    <simplelist type="inline">
+    <member><xref linkend="sql-creatematerializedview"></member>
     <member><xref linkend="sql-alterview"></member>
     <member><xref linkend="sql-dropview"></member>
    </simplelist>
*** /dev/null
--- b/doc/src/sgml/ref/drop_materialized_view.sgml
***************
*** 0 ****
--- 1,114 ----
+ <!--
+ doc/src/sgml/ref/drop_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+ 
+ <refentry id="SQL-DROPMATERIALIZEDVIEW">
+  <refmeta>
+   <refentrytitle>DROP MATERIALIZED VIEW</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>DROP MATERIALIZED VIEW</refname>
+   <refpurpose>remove a materialized view</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-dropmaterializedview">
+   <primary>DROP MATERIALIZED VIEW</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ DROP MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1>
+   <title>Description</title>
+ 
+   <para>
+    <command>DROP MATERIALIZED VIEW</command> drops an existing materialized
+    view. To execute this command you must be the owner of the materialized
+    view.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><literal>IF EXISTS</literal></term>
+     <listitem>
+      <para>
+       Do not throw an error if the materialized view does not exist. A notice
+       is issued in this case.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><replaceable class="PARAMETER">name</replaceable></term>
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the materialized view to
+       remove.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>CASCADE</literal></term>
+     <listitem>
+      <para>
+       Automatically drop objects that depend on the materialized view (such as
+       other materialized views, or regular views).
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>RESTRICT</literal></term>
+     <listitem>
+      <para>
+       Refuse to drop the materialized view if any objects depend on it.  This
+       is the default.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Examples</title>
+ 
+   <para>
+    This command will remove the materialized view called
+    <literal>order_summary</literal>:
+ <programlisting>
+ DROP MATERIALIZED VIEW order_summary;
+ </programlisting></para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Compatibility</title>
+ 
+   <para>
+    <command>DROP MATERIALIZED VIEW</command> is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>See Also</title>
+ 
+   <simplelist type="inline">
+    <member><xref linkend="sql-creatematerializedview"></member>
+    <member><xref linkend="sql-altermaterializedview"></member>
+    <member><xref linkend="sql-loadmaterializedview"></member>
+   </simplelist>
+  </refsect1>
+ 
+ </refentry>
*** /dev/null
--- b/doc/src/sgml/ref/load_materialized_view.sgml
***************
*** 0 ****
--- 1,83 ----
+ <!--
+ doc/src/sgml/ref/load_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+ 
+ <refentry id="SQL-LOADMATERIALIZEDVIEW">
+  <refmeta>
+   <refentrytitle>LOAD MATERIALIZED VIEW</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>LOAD MATERIALIZED VIEW</refname>
+   <refpurpose>refresh a materialized view</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-dropmaterializedview">
+   <primary>LOAD MATERIALIZED VIEW</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ LOAD MATERIALIZED VIEW <replaceable class="PARAMETER">name</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1>
+   <title>Description</title>
+ 
+   <para>
+    <command>LOAD MATERIALIZED VIEW</command> refreshes the contents of a
+    materialized view by re-executing the backing query.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Parameters</title>
+ 
+   <variablelist>
+    <varlistentry>
+     <term><replaceable class="PARAMETER">name</replaceable></term>
+     <listitem>
+      <para>
+       The name (optionally schema-qualified) of the materialized view to
+       refresh.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Examples</title>
+ 
+   <para>
+    This command will refresh the materialized view called
+    <literal>order_summary</literal>:
+ <programlisting>
+ LOAD MATERIALIZED VIEW order_summary;
+ </programlisting></para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Compatibility</title>
+ 
+   <para>
+    <command>LOAD MATERIALIZED VIEW</command> is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>See Also</title>
+ 
+   <simplelist type="inline">
+    <member><xref linkend="sql-creatematerializedview"></member>
+    <member><xref linkend="sql-altermaterializedview"></member>
+    <member><xref linkend="sql-dropmaterializedview"></member>
+   </simplelist>
+  </refsect1>
+ 
+ </refentry>
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 32,37 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
--- 32,38 ----
    FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable>
    FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
    LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> |
+   MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
    [ PROCEDURAL ] LANGUAGE <replaceable class="PARAMETER">object_name</replaceable> |
    ROLE <replaceable class="PARAMETER">object_name</replaceable> |
    SCHEMA <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 49,54 ****
--- 49,55 ----
     &alterIndex;
     &alterLanguage;
     &alterLargeObject;
+    &alterMaterializedView;
     &alterOperator;
     &alterOperatorClass;
     &alterOperatorFamily;
***************
*** 90,95 ****
--- 91,97 ----
     &createGroup;
     &createIndex;
     &createLanguage;
+    &createMaterializedView;
     &createOperator;
     &createOperatorClass;
     &createOperatorFamily;
***************
*** 129,134 ****
--- 131,137 ----
     &dropGroup;
     &dropIndex;
     &dropLanguage;
+    &dropMaterializedView;
     &dropOperator;
     &dropOperatorClass;
     &dropOperatorFamily;
***************
*** 157,162 ****
--- 160,166 ----
     &insert;
     &listen;
     &load;
+    &loadMaterializedView;
     &lock;
     &move;
     &notify;
*** a/src/backend/access/common/reloptions.c
--- b/src/backend/access/common/reloptions.c
***************
*** 791,796 **** extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
--- 791,797 ----
  		case RELKIND_RELATION:
  		case RELKIND_TOASTVALUE:
  		case RELKIND_VIEW:
+ 		case RELKIND_MATVIEW:
  			options = heap_reloptions(classForm->relkind, datum, false);
  			break;
  		case RELKIND_INDEX:
***************
*** 1191,1196 **** heap_reloptions(char relkind, Datum reloptions, bool validate)
--- 1192,1198 ----
  			}
  			return (bytea *) rdopts;
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  			return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
  		case RELKIND_VIEW:
  			return default_reloptions(reloptions, validate, RELOPT_KIND_VIEW);
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 2087,2093 **** heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
  	 * If the new tuple is too big for storage or contains already toasted
  	 * out-of-line attributes from some other relation, invoke the toaster.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(tup));
--- 2087,2094 ----
  	 * If the new tuple is too big for storage or contains already toasted
  	 * out-of-line attributes from some other relation, invoke the toaster.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
! 		relation->rd_rel->relkind != RELKIND_MATVIEW)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(tup));
***************
*** 2628,2634 **** l1:
  	 * because we need to look at the contents of the tuple, but it's OK to
  	 * release the content lock on the buffer first.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(&tp));
--- 2629,2636 ----
  	 * because we need to look at the contents of the tuple, but it's OK to
  	 * release the content lock on the buffer first.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
! 		relation->rd_rel->relkind != RELKIND_MATVIEW)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(&tp));
***************
*** 2987,2993 **** l2:
  	 * We need to invoke the toaster if there are already any out-of-line
  	 * toasted values present, or if the new tuple is over-threshold.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(&oldtup));
--- 2989,2996 ----
  	 * We need to invoke the toaster if there are already any out-of-line
  	 * toasted values present, or if the new tuple is over-threshold.
  	 */
! 	if (relation->rd_rel->relkind != RELKIND_RELATION &&
! 		relation->rd_rel->relkind != RELKIND_MATVIEW)
  	{
  		/* toast table entries should never be recursively toasted */
  		Assert(!HeapTupleHasExternal(&oldtup));
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
***************
*** 353,362 **** toast_delete(Relation rel, HeapTuple oldtup)
  	bool		toast_isnull[MaxHeapAttributeNumber];
  
  	/*
! 	 * We should only ever be called for tuples of plain relations ---
! 	 * recursing on a toast rel is bad news.
  	 */
! 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
  
  	/*
  	 * Get the tuple descriptor and break down the tuple into fields.
--- 353,363 ----
  	bool		toast_isnull[MaxHeapAttributeNumber];
  
  	/*
! 	 * We should only ever be called for tuples of plain relations or
! 	 * materialized views --- recursing on a toast rel is bad news.
  	 */
! 	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
! 		   rel->rd_rel->relkind == RELKIND_MATVIEW);
  
  	/*
  	 * Get the tuple descriptor and break down the tuple into fields.
***************
*** 443,449 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
  	 * We should only ever be called for tuples of plain relations ---
  	 * recursing on a toast rel is bad news.
  	 */
! 	Assert(rel->rd_rel->relkind == RELKIND_RELATION);
  
  	/*
  	 * Get the tuple descriptor and break down the tuple(s) into fields.
--- 444,451 ----
  	 * We should only ever be called for tuples of plain relations ---
  	 * recursing on a toast rel is bad news.
  	 */
! 	Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
! 		   rel->rd_rel->relkind == RELKIND_MATVIEW);
  
  	/*
  	 * Get the tuple descriptor and break down the tuple(s) into fields.
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 765,770 **** objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
--- 765,772 ----
  				objects = list_concat(objects, objs);
  				objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
  				objects = list_concat(objects, objs);
+ 				objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
+ 				objects = list_concat(objects, objs);
  				objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
  				objects = list_concat(objects, objs);
  				break;
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 3013,3018 **** getRelationDescription(StringInfo buffer, Oid relid)
--- 3013,3022 ----
  			appendStringInfo(buffer, _("view %s"),
  							 relname);
  			break;
+ 		case RELKIND_MATVIEW:
+ 			appendStringInfo(buffer, _("materialized view %s"),
+ 							 relname);
+ 			break;
  		case RELKIND_COMPOSITE_TYPE:
  			appendStringInfo(buffer, _("composite type %s"),
  							 relname);
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 777,782 **** InsertPgClassTuple(Relation pg_class_desc,
--- 777,783 ----
  	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
  	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
  	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
+ 	values[Anum_pg_class_relisvalid - 1] = BoolGetDatum(rd_rel->relisvalid);
  	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
  	if (relacl != (Datum) 0)
  		values[Anum_pg_class_relacl - 1] = relacl;
***************
*** 832,837 **** AddNewRelationTuple(Relation pg_class_desc,
--- 833,839 ----
  	switch (relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_INDEX:
  		case RELKIND_TOASTVALUE:
  			/* The relation is real, but as yet empty */
***************
*** 855,860 **** AddNewRelationTuple(Relation pg_class_desc,
--- 857,863 ----
  
  	/* Initialize relfrozenxid */
  	if (relkind == RELKIND_RELATION ||
+ 		relkind == RELKIND_MATVIEW ||
  		relkind == RELKIND_TOASTVALUE)
  	{
  		/*
***************
*** 878,883 **** AddNewRelationTuple(Relation pg_class_desc,
--- 881,887 ----
  	new_rel_reltup->relowner = relowner;
  	new_rel_reltup->reltype = new_type_oid;
  	new_rel_reltup->reloftype = reloftype;
+ 	new_rel_reltup->relisvalid = true;
  
  	new_rel_desc->rd_att->tdtypeid = new_type_oid;
  
***************
*** 1056,1063 **** heap_create_with_catalog(const char *relname,
  		if (IsBinaryUpgrade &&
  			OidIsValid(binary_upgrade_next_heap_pg_class_oid) &&
  			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! 			 relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE ||
! 			 relkind == RELKIND_FOREIGN_TABLE))
  		{
  			relid = binary_upgrade_next_heap_pg_class_oid;
  			binary_upgrade_next_heap_pg_class_oid = InvalidOid;
--- 1060,1067 ----
  		if (IsBinaryUpgrade &&
  			OidIsValid(binary_upgrade_next_heap_pg_class_oid) &&
  			(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! 			 relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
! 			 relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
  		{
  			relid = binary_upgrade_next_heap_pg_class_oid;
  			binary_upgrade_next_heap_pg_class_oid = InvalidOid;
***************
*** 1083,1088 **** heap_create_with_catalog(const char *relname,
--- 1087,1093 ----
  		{
  			case RELKIND_RELATION:
  			case RELKIND_VIEW:
+ 			case RELKIND_MATVIEW:
  			case RELKIND_FOREIGN_TABLE:
  				relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
  											  relnamespace);
***************
*** 1126,1131 **** heap_create_with_catalog(const char *relname,
--- 1131,1137 ----
  	 */
  	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
  							  relkind == RELKIND_VIEW ||
+ 							  relkind == RELKIND_MATVIEW ||
  							  relkind == RELKIND_FOREIGN_TABLE ||
  							  relkind == RELKIND_COMPOSITE_TYPE))
  		new_array_oid = AssignTypeArrayOid();
***************
*** 1303,1309 **** heap_create_with_catalog(const char *relname,
  
  	if (relpersistence == RELPERSISTENCE_UNLOGGED)
  	{
! 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE);
  		heap_create_init_fork(new_rel_desc);
  	}
  
--- 1309,1316 ----
  
  	if (relpersistence == RELPERSISTENCE_UNLOGGED)
  	{
! 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
! 			   relkind == RELKIND_TOASTVALUE);
  		heap_create_init_fork(new_rel_desc);
  	}
  
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 442,447 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 442,448 ----
  			case OBJECT_SEQUENCE:
  			case OBJECT_TABLE:
  			case OBJECT_VIEW:
+ 			case OBJECT_MATVIEW:
  			case OBJECT_FOREIGN_TABLE:
  				address =
  					get_relation_by_qualified_name(objtype, objname,
***************
*** 814,819 **** get_relation_by_qualified_name(ObjectType objtype, List *objname,
--- 815,827 ----
  						 errmsg("\"%s\" is not a view",
  								RelationGetRelationName(relation))));
  			break;
+ 		case OBJECT_MATVIEW:
+ 			if (relation->rd_rel->relkind != RELKIND_MATVIEW)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 						 errmsg("\"%s\" is not a materialized view",
+ 								RelationGetRelationName(relation))));
+ 			break;
  		case OBJECT_FOREIGN_TABLE:
  			if (relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  				ereport(ERROR,
***************
*** 1071,1076 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1079,1085 ----
  		case OBJECT_SEQUENCE:
  		case OBJECT_TABLE:
  		case OBJECT_VIEW:
+ 		case OBJECT_MATVIEW:
  		case OBJECT_FOREIGN_TABLE:
  		case OBJECT_COLUMN:
  		case OBJECT_RULE:
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 84,93 **** BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
  
  	rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						relName)));
  
  	/* create_toast_table does all the work */
--- 84,94 ----
  
  	rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 		rel->rd_rel->relkind != RELKIND_MATVIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or materialized view",
  						relName)));
  
  	/* create_toast_table does all the work */
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
***************
*** 16,22 **** OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
  	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
  	dbcommands.o define.o discard.o dropcmds.o \
  	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
! 	indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
  	portalcmds.o prepare.o proclang.o \
  	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
  	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
--- 16,22 ----
  	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
  	dbcommands.o define.o discard.o dropcmds.o \
  	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
! 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
  	portalcmds.o prepare.o proclang.o \
  	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
  	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 118,123 **** ExecRenameStmt(RenameStmt *stmt)
--- 118,124 ----
  		case OBJECT_TABLE:
  		case OBJECT_SEQUENCE:
  		case OBJECT_VIEW:
+ 		case OBJECT_MATVIEW:
  		case OBJECT_INDEX:
  		case OBJECT_FOREIGN_TABLE:
  			RenameRelation(stmt);
***************
*** 189,194 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
--- 190,196 ----
  		case OBJECT_SEQUENCE:
  		case OBJECT_TABLE:
  		case OBJECT_VIEW:
+ 		case OBJECT_MATVIEW:
  		case OBJECT_FOREIGN_TABLE:
  			AlterTableNamespace(stmt);
  			break;
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 205,215 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
  	}
  
  	/*
! 	 * Check that it's a plain table or foreign table; we used to do this in
! 	 * get_rel_oids() but seems safer to check after we've locked the
! 	 * relation.
  	 */
! 	if (onerel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		/* Regular table, so we'll use the regular row acquisition function */
  		acquirefunc = acquire_sample_rows;
--- 205,216 ----
  	}
  
  	/*
! 	 * Check that it's a plain table, materialized view, or foreign table; we
! 	 * used to do this in get_rel_oids() but seems safer to check after we've
! 	 * locked the relation.
  	 */
! 	if (onerel->rd_rel->relkind == RELKIND_RELATION ||
! 		onerel->rd_rel->relkind == RELKIND_MATVIEW)
  	{
  		/* Regular table, so we'll use the regular row acquisition function */
  		acquirefunc = acquire_sample_rows;
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
***************
*** 83,97 **** CommentObject(CommentStmt *stmt)
  		case OBJECT_COLUMN:
  
  			/*
! 			 * Allow comments only on columns of tables, views, composite
! 			 * types, and foreign tables (which are the only relkinds for
! 			 * which pg_dump will dump per-column comments).  In particular we
! 			 * wish to disallow comments on index columns, because the naming
! 			 * of an index's columns may change across PG versions, so dumping
! 			 * per-column comments could create reload failures.
  			 */
  			if (relation->rd_rel->relkind != RELKIND_RELATION &&
  				relation->rd_rel->relkind != RELKIND_VIEW &&
  				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
  				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  				ereport(ERROR,
--- 83,99 ----
  		case OBJECT_COLUMN:
  
  			/*
! 			 * Allow comments only on columns of tables, views, materialized
! 			 * views, composite types, and foreign tables (which are the only
! 			 * relkinds for which pg_dump will dump per-column comments).  In
! 			 * particular we wish to disallow comments on index columns,
! 			 * because the naming of an index's columns may change across PG
! 			 * versions, so dumping per-column comments could create reload
! 			 * failures.
  			 */
  			if (relation->rd_rel->relkind != RELKIND_RELATION &&
  				relation->rd_rel->relkind != RELKIND_VIEW &&
+ 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
  				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
  				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  				ereport(ERROR,
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 1415,1420 **** BeginCopyTo(Relation rel,
--- 1415,1426 ----
  					 errmsg("cannot copy from view \"%s\"",
  							RelationGetRelationName(rel)),
  					 errhint("Try the COPY (SELECT ...) TO variant.")));
+ 		else if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("cannot copy from materialized view \"%s\"",
+ 							RelationGetRelationName(rel)),
+ 					 errhint("Try the COPY (SELECT ...) TO variant.")));
  		else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 1922,1927 **** CopyFrom(CopyState cstate)
--- 1928,1938 ----
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  					 errmsg("cannot copy to view \"%s\"",
  							RelationGetRelationName(cstate->rel))));
+ 		else if (cstate->rel->rd_rel->relkind == RELKIND_MATVIEW)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("cannot copy to materialized view \"%s\"",
+ 							RelationGetRelationName(cstate->rel))));
  		else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
*** a/src/backend/commands/createas.c
--- b/src/backend/commands/createas.c
***************
*** 2,7 ****
--- 2,9 ----
   *
   * createas.c
   *	  Execution of CREATE TABLE ... AS, a/k/a SELECT INTO
+  *	  Since CREATE MATERIALIZED VIEW shares syntax and most behaviors,
+  *	  implement that here, too.
   *
   * We implement this by diverting the query's normal output to a
   * specialized DestReceiver type.
***************
*** 29,34 ****
--- 31,38 ----
  #include "commands/createas.h"
  #include "commands/prepare.h"
  #include "commands/tablecmds.h"
+ #include "commands/view.h"
+ #include "parser/analyze.h"
  #include "parser/parse_clause.h"
  #include "rewrite/rewriteHandler.h"
  #include "storage/smgr.h"
***************
*** 43,48 **** typedef struct
--- 47,53 ----
  {
  	DestReceiver pub;			/* publicly-known function pointers */
  	IntoClause *into;			/* target relation specification */
+ 	Query		*query;			/* the query which defines/populates data */
  	/* These fields are filled by intorel_startup: */
  	Relation	rel;			/* relation to write to */
  	CommandId	output_cid;		/* cmin to insert in output tuples */
***************
*** 57,62 **** static void intorel_destroy(DestReceiver *self);
--- 62,130 ----
  
  
  /*
+  * Common setup needed by both normal execution and EXPLAIN ANALYZE.
+  */
+ Query *
+ SetupForCreateTableAs(Query *query, IntoClause *into, const char *queryString,
+ 					   ParamListInfo params, DestReceiver *dest)
+ {
+ 	Assert(query->commandType == CMD_SELECT);
+ 
+ 	/*
+ 	 * Parse analysis was done already, but we still have to run the rule
+ 	 * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
+ 	 * came straight from the parser, or suitable locks were acquired by
+ 	 * plancache.c.
+ 	 *
+ 	 * Because the rewriter and planner tend to scribble on the input, we make
+ 	 * a preliminary copy of the source querytree.	This prevents problems in
+ 	 * the case that CTAS is in a portal or plpgsql function and is executed
+ 	 * repeatedly.	(See also the same hack in EXPLAIN and PREPARE.)
+ 	 */
+ 	if (into->relkind == RELKIND_MATVIEW)
+ 		query = (Query *) parse_analyze((Node *) copyObject(query),
+ 										 queryString, NULL, 0)->utilityStmt;
+ 	else
+ 	{
+ 		List       *rewritten;
+ 
+ 		rewritten = QueryRewrite((Query *) copyObject(query));
+ 
+ 		/* SELECT should never rewrite to more or less than one SELECT query */
+ 		if (list_length(rewritten) != 1)
+ 			elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
+ 		query = (Query *) linitial(rewritten);
+ 	}
+ 
+ 	Assert(query->commandType == CMD_SELECT);
+ 
+ 	/* Save the query after rewrite but before planning. */
+ 	((DR_intorel *) dest)->query = query;
+ 	((DR_intorel *) dest)->into = into;
+ 
+ 	if (into->relkind == RELKIND_MATVIEW)
+ 	{
+ 		/*
+ 		 * A materialized view would either need to save parameters for use in
+ 		 * maintaining or loading the data or prohibit them entirely. The
+ 		 * latter seems safer and more sane.
+ 		 */
+ 		if (params != NULL && params->numParams > 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("materialized views may not be defined using bound parameters")));
+ 
+ 		/*
+ 		 * For a materialized view, we don't want the planner scribbling on
+ 		 * the query, because it will need to be planned again.
+ 		 */
+ 		query = (Query *) copyObject(query);
+ 	}
+ 
+ 	return query;
+ }
+ 
+ /*
   * ExecCreateTableAs -- execute a CREATE TABLE AS command
   */
  void
***************
*** 66,72 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  	Query	   *query = (Query *) stmt->query;
  	IntoClause *into = stmt->into;
  	DestReceiver *dest;
- 	List	   *rewritten;
  	PlannedStmt *plan;
  	QueryDesc  *queryDesc;
  	ScanDirection dir;
--- 134,139 ----
***************
*** 90,115 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  
  		return;
  	}
- 	Assert(query->commandType == CMD_SELECT);
  
! 	/*
! 	 * Parse analysis was done already, but we still have to run the rule
! 	 * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
! 	 * came straight from the parser, or suitable locks were acquired by
! 	 * plancache.c.
! 	 *
! 	 * Because the rewriter and planner tend to scribble on the input, we make
! 	 * a preliminary copy of the source querytree.	This prevents problems in
! 	 * the case that CTAS is in a portal or plpgsql function and is executed
! 	 * repeatedly.	(See also the same hack in EXPLAIN and PREPARE.)
! 	 */
! 	rewritten = QueryRewrite((Query *) copyObject(stmt->query));
! 
! 	/* SELECT should never rewrite to more or less than one SELECT query */
! 	if (list_length(rewritten) != 1)
! 		elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
! 	query = (Query *) linitial(rewritten);
! 	Assert(query->commandType == CMD_SELECT);
  
  	/* plan the query */
  	plan = pg_plan_query(query, 0, params);
--- 157,164 ----
  
  		return;
  	}
  
! 	query = SetupForCreateTableAs(query, into, queryString, params, dest);
  
  	/* plan the query */
  	plan = pg_plan_query(query, 0, params);
***************
*** 299,310 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
  	if (lc != NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
! 				 errmsg("CREATE TABLE AS specifies too many column names")));
  
  	/*
  	 * Actually create the target table
  	 */
! 	intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
  
  	/*
  	 * If necessary, create a TOAST table for the target table.  Note that
--- 348,399 ----
  	if (lc != NULL)
  		ereport(ERROR,
  				(errcode(ERRCODE_SYNTAX_ERROR),
! 				 errmsg("too many column names are specified")));
! 
! 	/*
! 	 * Enforce validations needed for materialized views only.
! 	 */
! 	if (into->relkind == RELKIND_MATVIEW)
! 	{
! 		/*
! 		* Prohibit a data-modifying CTE in the query used to create a
! 		* materialized view. It's not sufficiently clear what the user would
! 		* want to happen if the MV is refreshed or incrementally maintained.
! 		*/
! 		if (myState->query->hasModifyingCTE)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("materialized views must not use data-modifying statements in WITH")));
! 
! 		/*
! 		 * Unlogged materialized views would be useful, but there has not yet
! 		 * been a solution to the question of how to flag them as invalid
! 		 * after a crash.
! 		 */
! 		if (into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("unlogged materialized views are not supported")));
! 
! 		/*
! 		 * If the user didn't explicitly ask for a temporary materialized
! 		 * view, check whether any temporary database objects are used in its
! 		 * creation query. It would be hard to refresh data or incrementally
! 		 * maintain it if a source disappeared.
! 		 */
! 		if (into->rel->relpersistence == RELPERSISTENCE_PERMANENT
! 			&& isQueryUsingTempRelation(myState->query))
! 		{
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("permanent materialized views must not use temporary tables or views")));
! 		}
! 	}
  
  	/*
  	 * Actually create the target table
  	 */
! 	intoRelationId = DefineRelation(create, into->relkind, InvalidOid);
  
  	/*
  	 * If necessary, create a TOAST table for the target table.  Note that
***************
*** 330,335 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
--- 419,437 ----
  	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
  
  	/*
+ 	 * Create the "view" part of a materialized view.
+ 	 */
+ 	if (into->relkind == RELKIND_MATVIEW)
+ 	{
+ 		StoreViewQuery(intoRelationId, myState->query, false);
+ 		if (into->skipData)
+ 		{
+ 			CommandCounterIncrement();
+ 			SetRelationIsValid(intoRelationId, false);
+ 		}
+ 	}
+ 
+ 	/*
  	 * Check INSERT permission on the constructed table.
  	 *
  	 * XXX: It would arguably make sense to skip this check if into->skipData
***************
*** 338,344 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
  	rte = makeNode(RangeTblEntry);
  	rte->rtekind = RTE_RELATION;
  	rte->relid = intoRelationId;
! 	rte->relkind = RELKIND_RELATION;
  	rte->requiredPerms = ACL_INSERT;
  
  	for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
--- 440,446 ----
  	rte = makeNode(RangeTblEntry);
  	rte->rtekind = RTE_RELATION;
  	rte->relid = intoRelationId;
! 	rte->relkind = into->relkind;
  	rte->requiredPerms = ACL_INSERT;
  
  	for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 67,72 **** static event_trigger_support_data event_trigger_support[] = {
--- 67,73 ----
  	{ "FUNCTION", true },
  	{ "INDEX", true },
  	{ "LANGUAGE", true },
+ 	{ "MATERIALIZED VIEW", true },
  	{ "OPERATOR", true },
  	{ "OPERATOR CLASS", true },
  	{ "OPERATOR FAMILY", true },
***************
*** 216,221 **** check_ddl_tag(const char *tag)
--- 217,223 ----
  	 */
  	if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
  		pg_strcasecmp(tag, "SELECT INTO") == 0 ||
+ 		pg_strcasecmp(tag, "LOAD MATERIALIZED VIEW") == 0 ||
  		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
  		pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
  		return EVENT_TRIGGER_COMMAND_TAG_OK;
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 47,53 **** explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
  #define X_NOWHITESPACE 4
  
  static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! 				const char *queryString, ParamListInfo params);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				ExplainState *es);
  static double elapsed_time(instr_time *starttime);
--- 47,53 ----
  #define X_NOWHITESPACE 4
  
  static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! 				const char *queryString, DestReceiver *dest, ParamListInfo params);
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
  				ExplainState *es);
  static double elapsed_time(instr_time *starttime);
***************
*** 218,224 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
  		foreach(l, rewritten)
  		{
  			ExplainOneQuery((Query *) lfirst(l), NULL, &es,
! 							queryString, params);
  
  			/* Separate plans with an appropriate separator */
  			if (lnext(l) != NULL)
--- 218,224 ----
  		foreach(l, rewritten)
  		{
  			ExplainOneQuery((Query *) lfirst(l), NULL, &es,
! 							queryString, None_Receiver, params);
  
  			/* Separate plans with an appropriate separator */
  			if (lnext(l) != NULL)
***************
*** 299,310 **** ExplainResultDesc(ExplainStmt *stmt)
   */
  static void
  ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! 				const char *queryString, ParamListInfo params)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
! 		ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
  		return;
  	}
  
--- 299,312 ----
   */
  static void
  ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! 				const char *queryString, DestReceiver *dest,
! 				ParamListInfo params)
  {
  	/* planner will not cope with utility statements */
  	if (query->commandType == CMD_UTILITY)
  	{
! 		ExplainOneUtility(query->utilityStmt, into, es,
! 						  queryString, dest, params);
  		return;
  	}
  
***************
*** 319,325 **** ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
  		plan = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(plan, into, es, queryString, params);
  	}
  }
  
--- 321,327 ----
  		plan = pg_plan_query(query, 0, params);
  
  		/* run it (if needed) and produce output */
! 		ExplainOnePlan(plan, into, es, queryString, dest, params);
  	}
  }
  
***************
*** 336,342 **** ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
   */
  void
  ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
! 				  const char *queryString, ParamListInfo params)
  {
  	if (utilityStmt == NULL)
  		return;
--- 338,345 ----
   */
  void
  ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
! 				  const char *queryString, DestReceiver *dest,
! 				  ParamListInfo params)
  {
  	if (utilityStmt == NULL)
  		return;
***************
*** 349,361 **** ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
  		 * contained parsetree another time, but let's be safe.
  		 */
  		CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
! 		List	   *rewritten;
  
  		Assert(IsA(ctas->query, Query));
! 		rewritten = QueryRewrite((Query *) copyObject(ctas->query));
! 		Assert(list_length(rewritten) == 1);
! 		ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
! 						queryString, params);
  	}
  	else if (IsA(utilityStmt, ExecuteStmt))
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
--- 352,366 ----
  		 * contained parsetree another time, but let's be safe.
  		 */
  		CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
! 		Query	   *query = (Query *) ctas->query;
! 
! 		dest = CreateIntoRelDestReceiver(into);
  
  		Assert(IsA(ctas->query, Query));
! 
! 		query = SetupForCreateTableAs(query, ctas->into, queryString, params, dest);
! 
! 		ExplainOneQuery(query, ctas->into, es, queryString, dest, params);
  	}
  	else if (IsA(utilityStmt, ExecuteStmt))
  		ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
***************
*** 396,404 **** ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
   */
  void
  ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
! 			   const char *queryString, ParamListInfo params)
  {
- 	DestReceiver *dest;
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
--- 401,408 ----
   */
  void
  ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
! 			   const char *queryString, DestReceiver *dest, ParamListInfo params)
  {
  	QueryDesc  *queryDesc;
  	instr_time	starttime;
  	double		totaltime = 0;
***************
*** 422,436 **** ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
  	PushCopiedSnapshot(GetActiveSnapshot());
  	UpdateActiveSnapshotCommandId();
  
- 	/*
- 	 * Normally we discard the query's output, but if explaining CREATE TABLE
- 	 * AS, we'd better use the appropriate tuple receiver.
- 	 */
- 	if (into)
- 		dest = CreateIntoRelDestReceiver(into);
- 	else
- 		dest = None_Receiver;
- 
  	/* Create a QueryDesc for the query */
  	queryDesc = CreateQueryDesc(plannedstmt, queryString,
  								GetActiveSnapshot(), InvalidSnapshot,
--- 426,431 ----
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 352,358 **** DefineIndex(IndexStmt *stmt,
  	relationId = RelationGetRelid(rel);
  	namespaceId = RelationGetNamespace(rel);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  	{
  		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  
--- 352,359 ----
  	relationId = RelationGetRelid(rel);
  	namespaceId = RelationGetNamespace(rel);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 		rel->rd_rel->relkind != RELKIND_MATVIEW)
  	{
  		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  
***************
*** 1860,1866 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
  	{
  		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
  
! 		if (classtuple->relkind != RELKIND_RELATION)
  			continue;
  
  		/* Skip temp tables of other backends; we can't reindex them at all */
--- 1861,1868 ----
  	{
  		Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
  
! 		if (classtuple->relkind != RELKIND_RELATION &&
! 			classtuple->relkind != RELKIND_MATVIEW)
  			continue;
  
  		/* Skip temp tables of other backends; we can't reindex them at all */
*** /dev/null
--- b/src/backend/commands/matview.c
***************
*** 0 ****
--- 1,314 ----
+ /*-------------------------------------------------------------------------
+  *
+  * matview.c
+  *	  materialized view support
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  *
+  * IDENTIFICATION
+  *	  src/backend/commands/matview.c
+  *
+  *-------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ 
+ #include "access/relscan.h"
+ #include "access/xact.h"
+ #include "catalog/catalog.h"
+ #include "catalog/namespace.h"
+ #include "commands/cluster.h"
+ #include "commands/matview.h"
+ #include "commands/tablecmds.h"
+ #include "executor/executor.h"
+ #include "miscadmin.h"
+ #include "storage/lmgr.h"
+ #include "storage/smgr.h"
+ #include "tcop/tcopprot.h"
+ #include "utils/snapmgr.h"
+ 
+ 
+ typedef struct
+ {
+ 	DestReceiver pub;			/* publicly-known function pointers */
+ 	Oid			transientoid;	/* OID of new heap into which to store */
+ 	/* These fields are filled by transientrel_startup: */
+ 	Relation	transientrel;	/* relation to write to */
+ 	CommandId	output_cid;		/* cmin to insert in output tuples */
+ 	int			hi_options;		/* heap_insert performance options */
+ 	BulkInsertState bistate;	/* bulk insert state */
+ } DR_transientrel;
+ 
+ static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
+ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
+ static void transientrel_shutdown(DestReceiver *self);
+ static void transientrel_destroy(DestReceiver *self);
+ static void load_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ 						  Query *dataQuery, const char *queryString);
+ 
+ /*
+  * ExecLoadMatView -- execute a LOAD MATERIALIZED VIEW command
+  */
+ void
+ ExecLoadMatView(LoadMatViewStmt *stmt, const char *queryString,
+ 				  ParamListInfo params, char *completionTag)
+ {
+ 	Oid			matviewOid;
+ 	Relation	matviewRel;
+ 	RewriteRule *rule;
+ 	List	   *actions;
+ 	Query	   *dataQuery;
+ 	Oid			tableSpace;
+ 	bool		isWithOids;
+ 
+ 	/*
+ 	 * Get a lock until end of transaction.
+ 	 */
+ 	matviewOid = RangeVarGetRelidExtended(stmt->relation,
+ 										   AccessExclusiveLock, false, false,
+ 										   RangeVarCallbackOwnsTable, NULL);
+ 	matviewRel = heap_open(matviewOid, NoLock);
+ 
+ 	/* Make sure it is a materialized view. */
+ 	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("\"%s\" is not a materialized view",
+ 						RelationGetRelationName(matviewRel))));
+ 
+ 	/*
+ 	 * We're not using materialized views in the system catalogs.
+ 	 */
+ 	Assert(!IsSystemRelation(matviewRel));
+ 
+ 	/*
+ 	 * Reject clustering a remote temp table ... their local buffer
+ 	 * manager is not going to cope.
+ 	 */
+ 	if (RELATION_IS_OTHER_TEMP(matviewRel))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("cannot load temporary materialized views of other sessions")));
+ 
+ 	/*
+ 	 * Check that everything is correct for a load. Problems at this point are
+ 	 * internal errors, so elog is sufficient.
+ 	 */
+ 	if (matviewRel->rd_rel->relhasrules == false ||
+ 		matviewRel->rd_rules->numLocks < 1)
+ 		elog(ERROR,
+ 			 "materialized view \"%s\" is missing rewrite information",
+ 			 RelationGetRelationName(matviewRel));
+ 
+ 	if (matviewRel->rd_rules->numLocks > 1)
+ 		elog(ERROR,
+ 			 "materialized view \"%s\" has too many rules",
+ 			 RelationGetRelationName(matviewRel));
+ 
+ 	rule = matviewRel->rd_rules->rules[0];
+ 	if (rule->event != CMD_SELECT || !(rule->isInstead))
+ 		elog(ERROR,
+ 			 "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+ 			 RelationGetRelationName(matviewRel));
+ 
+ 	actions = rule->actions;
+ 	if (list_length(actions) != 1)
+ 		elog(ERROR,
+ 			 "the rule for materialized view \"%s\" is not a single action",
+ 			 RelationGetRelationName(matviewRel));
+ 
+ 	/*
+ 	 * The stored query was rewritten at the time of the MV definition, but
+ 	 * has not been scribbled on by the planner.
+ 	 */
+ 	dataQuery = (Query *) linitial(actions);
+ 	Assert(IsA(dataQuery, Query));
+ 
+ 	/*
+ 	 * Check for active uses of the relation in the current transaction, such
+ 	 * as open scans.
+ 	 */
+ 	CheckTableNotInUse(matviewRel, "LOAD MATERIALIZED VIEW");
+ 
+ 	tableSpace = matviewRel->rd_rel->reltablespace;
+ 	isWithOids = matviewRel->rd_rel->relhasoids;
+ 
+ 	heap_close(matviewRel, NoLock);
+ 
+ 	load_matview(matviewOid, tableSpace, isWithOids, dataQuery, queryString);
+ }
+ 
+ /*
+  * load_matview
+  *
+  * This loads the materialized view by creating a new table and swapping the
+  * relfilenodes of the new table and the old materialized view, so the OID of
+  * the original materialized view is preserved. Thus we do not lose GRANT nor
+  * references to this materialized view.
+  *
+  * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
+  * the new heap, it's better to create the indexes afterwards than to fill them
+  * incrementally while we load.
+  *
+  * If the materialized view was flagged with relisvalid == false, success of
+  * this command will change it to true.
+  */
+ static void
+ load_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ 			 Query *dataQuery, const char *queryString)
+ {
+ 	Oid			OIDNewHeap;
+ 	PlannedStmt *plan;
+ 	DestReceiver *dest;
+ 	QueryDesc  *queryDesc;
+ 
+ 	/* Check for user-requested abort. */
+ 	CHECK_FOR_INTERRUPTS();
+ 
+ 	/* Plan the query which will generate data for the load. */
+ 	plan = pg_plan_query(dataQuery, 0, NULL);
+ 
+ 	/* Create the transient table that will receive the regenerated data. */
+ 	OIDNewHeap = make_new_heap(matviewOid, tableSpace);
+ 	dest = CreateTransientRelDestReceiver(OIDNewHeap);
+ 
+ 	/*
+ 	 * Use a snapshot with an updated command ID to ensure this query sees
+ 	 * results of any previously executed queries.	(This could only matter if
+ 	 * the planner executed an allegedly-stable function that changed the
+ 	 * database contents, but let's do it anyway to be safe.)
+ 	 */
+ 	PushCopiedSnapshot(GetActiveSnapshot());
+ 	UpdateActiveSnapshotCommandId();
+ 
+ 	/* Create a QueryDesc, redirecting output to our tuple receiver */
+ 	queryDesc = CreateQueryDesc(plan, queryString,
+ 								GetActiveSnapshot(), InvalidSnapshot,
+ 								dest, NULL, 0);
+ 
+ 	/* call ExecutorStart to prepare the plan for execution */
+ 	ExecutorStart(queryDesc,
+ 				  isWithOids ? EXEC_FLAG_WITH_OIDS : EXEC_FLAG_WITHOUT_OIDS);
+ 
+ 	/* run the plan */
+ 	ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+ 
+ 	/* and clean up */
+ 	ExecutorFinish(queryDesc);
+ 	ExecutorEnd(queryDesc);
+ 
+ 	FreeQueryDesc(queryDesc);
+ 
+ 	PopActiveSnapshot();
+ 
+ 	/*
+ 	 * Swap the physical files of the target and transient tables, then
+ 	 * rebuild the target's indexes and throw away the transient table.
+ 	 */
+ 	finish_heap_swap(matviewOid, OIDNewHeap, false, false, false, RecentXmin);
+ 
+ 	SetRelationIsValid(matviewOid, true);
+ }
+ 
+ DestReceiver *
+ CreateTransientRelDestReceiver(Oid transientoid)
+ {
+ 	DR_transientrel *self = (DR_transientrel *) palloc0(sizeof(DR_transientrel));
+ 
+ 	self->pub.receiveSlot = transientrel_receive;
+ 	self->pub.rStartup = transientrel_startup;
+ 	self->pub.rShutdown = transientrel_shutdown;
+ 	self->pub.rDestroy = transientrel_destroy;
+ 	self->pub.mydest = DestTransientRel;
+ 	self->transientoid = transientoid;
+ 
+ 	return (DestReceiver *) self;
+ }
+ 
+ /*
+  * transientrel_startup --- executor startup
+  */
+ static void
+ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+ {
+ 	DR_transientrel *myState = (DR_transientrel *) self;
+ 	Relation transientrel;
+ 
+ 	transientrel = heap_open(myState->transientoid, NoLock);
+ 
+ 	/*
+ 	 * Fill private fields of myState for use by later routines
+ 	 */
+ 	myState->transientrel = transientrel;
+ 	myState->output_cid = GetCurrentCommandId(true);
+ 
+ 	/*
+ 	 * We can skip WAL-logging the insertions, unless PITR or streaming
+ 	 * replication is in use. We can skip the FSM in any case.
+ 	 */
+ 	myState->hi_options = HEAP_INSERT_SKIP_FSM |
+ 		(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
+ 	myState->bistate = GetBulkInsertState();
+ 
+ 	/* Not using WAL requires smgr_targblock be initially invalid */
+ 	Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
+ }
+ 
+ /*
+  * transientrel_receive --- receive one tuple
+  */
+ static void
+ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
+ {
+ 	DR_transientrel *myState = (DR_transientrel *) self;
+ 	HeapTuple	tuple;
+ 
+ 	/*
+ 	 * get the heap tuple out of the tuple table slot, making sure we have a
+ 	 * writable copy
+ 	 */
+ 	tuple = ExecMaterializeSlot(slot);
+ 
+ 	/*
+ 	 * force assignment of new OID (see comments in ExecInsert)
+ 	 */
+ 	if (myState->transientrel->rd_rel->relhasoids)
+ 		HeapTupleSetOid(tuple, InvalidOid);
+ 
+ 	heap_insert(myState->transientrel,
+ 				tuple,
+ 				myState->output_cid,
+ 				myState->hi_options,
+ 				myState->bistate);
+ 
+ 	/* We know this is a newly created relation, so there are no indexes */
+ }
+ 
+ /*
+  * transientrel_shutdown --- executor end
+  */
+ static void
+ transientrel_shutdown(DestReceiver *self)
+ {
+ 	DR_transientrel *myState = (DR_transientrel *) self;
+ 
+ 	FreeBulkInsertState(myState->bistate);
+ 
+ 	/* If we skipped using WAL, must heap_sync before commit */
+ 	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
+ 		heap_sync(myState->transientrel);
+ 
+ 	/* close transientrel, but keep lock until commit */
+ 	heap_close(myState->transientrel, NoLock);
+ 	myState->transientrel = NULL;
+ }
+ 
+ /*
+  * transientrel_destroy --- release DestReceiver object
+  */
+ static void
+ transientrel_destroy(DestReceiver *self)
+ {
+ 	pfree(self);
+ }
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 665,673 **** ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
  		PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
  
  		if (IsA(pstmt, PlannedStmt))
! 			ExplainOnePlan(pstmt, into, es, query_string, paramLI);
  		else
! 			ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
--- 665,673 ----
  		PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
  
  		if (IsA(pstmt, PlannedStmt))
! 			ExplainOnePlan(pstmt, into, es, query_string, None_Receiver, paramLI);
  		else
! 			ExplainOneUtility((Node *) pstmt, into, es, query_string, None_Receiver, paramLI);
  
  		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
  
*** a/src/backend/commands/seclabel.c
--- b/src/backend/commands/seclabel.c
***************
*** 101,111 **** ExecSecLabelStmt(SecLabelStmt *stmt)
  
  			/*
  			 * Allow security labels only on columns of tables, views,
! 			 * composite types, and foreign tables (which are the only
! 			 * relkinds for which pg_dump will dump labels).
  			 */
  			if (relation->rd_rel->relkind != RELKIND_RELATION &&
  				relation->rd_rel->relkind != RELKIND_VIEW &&
  				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
  				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  				ereport(ERROR,
--- 101,112 ----
  
  			/*
  			 * Allow security labels only on columns of tables, views,
! 			 * materialized views, composite types, and foreign tables (which
! 			 * are the only relkinds for which pg_dump will dump labels).
  			 */
  			if (relation->rd_rel->relkind != RELKIND_RELATION &&
  				relation->rd_rel->relkind != RELKIND_VIEW &&
+ 				relation->rd_rel->relkind != RELKIND_MATVIEW &&
  				relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
  				relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  				ereport(ERROR,
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 215,220 **** static const struct dropmsgstrings dropmsgstringarray[] = {
--- 215,226 ----
  		gettext_noop("view \"%s\" does not exist, skipping"),
  		gettext_noop("\"%s\" is not a view"),
  	gettext_noop("Use DROP VIEW to remove a view.")},
+ 	{RELKIND_MATVIEW,
+ 		ERRCODE_UNDEFINED_TABLE,
+ 		gettext_noop("materialized view \"%s\" does not exist"),
+ 		gettext_noop("materialized view \"%s\" does not exist, skipping"),
+ 		gettext_noop("\"%s\" is not a materialized view"),
+ 	gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
  	{RELKIND_INDEX,
  		ERRCODE_UNDEFINED_OBJECT,
  		gettext_noop("index \"%s\" does not exist"),
***************
*** 246,254 **** struct DropRelationCallbackState
  /* Alter table target-type flags for ATSimplePermissions */
  #define		ATT_TABLE				0x0001
  #define		ATT_VIEW				0x0002
! #define		ATT_INDEX				0x0004
! #define		ATT_COMPOSITE_TYPE		0x0008
! #define		ATT_FOREIGN_TABLE		0x0010
  
  static void truncate_check_rel(Relation rel);
  static List *MergeAttributes(List *schema, List *supers, char relpersistence,
--- 252,261 ----
  /* Alter table target-type flags for ATSimplePermissions */
  #define		ATT_TABLE				0x0001
  #define		ATT_VIEW				0x0002
! #define		ATT_MATVIEW				0x0004
! #define		ATT_INDEX				0x0008
! #define		ATT_COMPOSITE_TYPE		0x0010
! #define		ATT_FOREIGN_TABLE		0x0020
  
  static void truncate_check_rel(Relation rel);
  static List *MergeAttributes(List *schema, List *supers, char relpersistence,
***************
*** 397,402 **** static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
--- 404,411 ----
  static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
  								 Oid oldrelid, void *arg);
  
+ static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+ 
  
  /* ----------------------------------------------------------------
   *		DefineRelation
***************
*** 733,739 **** DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
  /*
   * RemoveRelations
   *		Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
!  *		DROP FOREIGN TABLE
   */
  void
  RemoveRelations(DropStmt *drop)
--- 742,748 ----
  /*
   * RemoveRelations
   *		Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
!  *		DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
   */
  void
  RemoveRelations(DropStmt *drop)
***************
*** 782,787 **** RemoveRelations(DropStmt *drop)
--- 791,800 ----
  			relkind = RELKIND_VIEW;
  			break;
  
+ 		case OBJECT_MATVIEW:
+ 			relkind = RELKIND_MATVIEW;
+ 			break;
+ 
  		case OBJECT_FOREIGN_TABLE:
  			relkind = RELKIND_FOREIGN_TABLE;
  			break;
***************
*** 1163,1168 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1176,1188 ----
  			heap_relid = RelationGetRelid(rel);
  			toast_relid = rel->rd_rel->reltoastrelid;
  
+ 			/* This makes a materialized view invalid for use. */
+ 			if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
+ 				rel->rd_rel->relisvalid)
+ 			{
+ 				SetRelationIsValid(heap_relid, false);
+ 			}
+ 
  			/*
  			 * The same for the toast table, if any.
  			 */
***************
*** 1226,1236 **** truncate_check_rel(Relation rel)
  {
  	AclResult	aclresult;
  
! 	/* Only allow truncate on regular tables */
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						RelationGetRelationName(rel))));
  
  	/* Permissions checks */
--- 1246,1257 ----
  {
  	AclResult	aclresult;
  
! 	/* Only allow truncate on regular tables and materialized views. */
! 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 		rel->rd_rel->relkind != RELKIND_MATVIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or materialized view",
  						RelationGetRelationName(rel))));
  
  	/* Permissions checks */
***************
*** 2044,2049 **** SetRelationHasSubclass(Oid relationId, bool relhassubclass)
--- 2065,2117 ----
  }
  
  /*
+  * SetRelationIsValid
+  *		Set the value of the relation's relisvalid field in pg_class.
+  *
+  * NOTE: caller must be holding an appropriate lock on the relation.
+  * ShareUpdateExclusiveLock is sufficient.
+  *
+  * NOTE: an important side-effect of this operation is that an SI invalidation
+  * message is sent out to all backends --- including me --- causing plans
+  * referencing the relation to be rebuilt with the new list of children.
+  * This must happen even if we find that no change is needed in the pg_class
+  * row.
+  */
+ void
+ SetRelationIsValid(Oid relationId, bool relisvalid)
+ {
+ 	Relation	relationRelation;
+ 	HeapTuple	tuple;
+ 	Form_pg_class classtuple;
+ 
+ 	/*
+ 	 * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+ 	 */
+ 	relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
+ 	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for relation %u", relationId);
+ 	classtuple = (Form_pg_class) GETSTRUCT(tuple);
+ 
+ 	if (classtuple->relisvalid != relisvalid)
+ 	{
+ 		classtuple->relisvalid = relisvalid;
+ 		simple_heap_update(relationRelation, &tuple->t_self, tuple);
+ 
+ 		/* keep the catalog indexes up to date */
+ 		CatalogUpdateIndexes(relationRelation, tuple);
+ 	}
+ 	else
+ 	{
+ 		/* no need to change tuple, but force relcache rebuild anyway */
+ 		CacheInvalidateRelcacheByTuple(tuple);
+ 	}
+ 
+ 	heap_freetuple(tuple);
+ 	heap_close(relationRelation, RowExclusiveLock);
+ }
+ 
+ /*
   *		renameatt_check			- basic sanity checks before attribute rename
   */
  static void
***************
*** 2065,2076 **** renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
  	 */
  	if (relkind != RELKIND_RELATION &&
  		relkind != RELKIND_VIEW &&
  		relkind != RELKIND_COMPOSITE_TYPE &&
  		relkind != RELKIND_INDEX &&
  		relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table, view, composite type, index, or foreign table",
  						NameStr(classform->relname))));
  
  	/*
--- 2133,2145 ----
  	 */
  	if (relkind != RELKIND_RELATION &&
  		relkind != RELKIND_VIEW &&
+ 		relkind != RELKIND_MATVIEW &&
  		relkind != RELKIND_COMPOSITE_TYPE &&
  		relkind != RELKIND_INDEX &&
  		relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
  						NameStr(classform->relname))));
  
  	/*
***************
*** 2978,2989 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			break;
  		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
  		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE);
  			/* This command never recurses */
  			pass = AT_PASS_MISC;
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
--- 3047,3058 ----
  			break;
  		case AT_SetOptions:		/* ALTER COLUMN SET ( options ) */
  		case AT_ResetOptions:	/* ALTER COLUMN RESET ( options ) */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE);
  			/* This command never recurses */
  			pass = AT_PASS_MISC;
  			break;
  		case AT_SetStorage:		/* ALTER COLUMN SET STORAGE */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
  			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
***************
*** 2996,3002 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_DROP;
  			break;
  		case AT_AddIndex:		/* ADD INDEX */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* This command never recurses */
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_INDEX;
--- 3065,3071 ----
  			pass = AT_PASS_DROP;
  			break;
  		case AT_AddIndex:		/* ADD INDEX */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
  			/* This command never recurses */
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_INDEX;
***************
*** 3043,3049 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			break;
  		case AT_ClusterOn:		/* CLUSTER ON */
  		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
! 			ATSimplePermissions(rel, ATT_TABLE);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
--- 3112,3118 ----
  			break;
  		case AT_ClusterOn:		/* CLUSTER ON */
  		case AT_DropCluster:	/* SET WITHOUT CLUSTER */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
***************
*** 3070,3076 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetTableSpace:	/* SET TABLESPACE */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
  			/* This command never recurses */
  			ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
  			pass = AT_PASS_MISC;	/* doesn't actually matter */
--- 3139,3145 ----
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetTableSpace:	/* SET TABLESPACE */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX);
  			/* This command never recurses */
  			ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
  			pass = AT_PASS_MISC;	/* doesn't actually matter */
***************
*** 3078,3084 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_SetRelOptions:	/* SET (...) */
  		case AT_ResetRelOptions:		/* RESET (...) */
  		case AT_ReplaceRelOptions:		/* reset them all, then set just these */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW);
  			/* This command never recurses */
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
--- 3147,3153 ----
  		case AT_SetRelOptions:	/* SET (...) */
  		case AT_ResetRelOptions:		/* RESET (...) */
  		case AT_ReplaceRelOptions:		/* reset them all, then set just these */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
  			/* This command never recurses */
  			/* No command-specific prep needed */
  			pass = AT_PASS_MISC;
***************
*** 3191,3197 **** ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  	{
  		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  
! 		if (tab->relkind == RELKIND_RELATION)
  			AlterTableCreateToastTable(tab->relid, (Datum) 0);
  	}
  }
--- 3260,3267 ----
  	{
  		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  
! 		if (tab->relkind == RELKIND_RELATION ||
! 			tab->relkind == RELKIND_MATVIEW)
  			AlterTableCreateToastTable(tab->relid, (Datum) 0);
  	}
  }
***************
*** 3923,3928 **** ATSimplePermissions(Relation rel, int allowed_targets)
--- 3993,4001 ----
  		case RELKIND_VIEW:
  			actual_target = ATT_VIEW;
  			break;
+ 		case RELKIND_MATVIEW:
+ 			actual_target = ATT_MATVIEW;
+ 			break;
  		case RELKIND_INDEX:
  			actual_target = ATT_INDEX;
  			break;
***************
*** 3969,3986 **** ATWrongRelkindError(Relation rel, int allowed_targets)
  		case ATT_TABLE:
  			msg = _("\"%s\" is not a table");
  			break;
- 		case ATT_TABLE | ATT_INDEX:
- 			msg = _("\"%s\" is not a table or index");
- 			break;
  		case ATT_TABLE | ATT_VIEW:
  			msg = _("\"%s\" is not a table or view");
  			break;
  		case ATT_TABLE | ATT_FOREIGN_TABLE:
  			msg = _("\"%s\" is not a table or foreign table");
  			break;
  		case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
  			msg = _("\"%s\" is not a table, composite type, or foreign table");
  			break;
  		case ATT_VIEW:
  			msg = _("\"%s\" is not a view");
  			break;
--- 4042,4068 ----
  		case ATT_TABLE:
  			msg = _("\"%s\" is not a table");
  			break;
  		case ATT_TABLE | ATT_VIEW:
  			msg = _("\"%s\" is not a table or view");
  			break;
+ 		case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX:
+ 			msg = _("\"%s\" is not a table, view, materialized view, or index");
+ 			break;
+ 		case ATT_TABLE | ATT_MATVIEW:
+ 			msg = _("\"%s\" is not a table or materialized view");
+ 			break;
+ 		case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
+ 			msg = _("\"%s\" is not a table, materialized view, or index");
+ 			break;
  		case ATT_TABLE | ATT_FOREIGN_TABLE:
  			msg = _("\"%s\" is not a table or foreign table");
  			break;
  		case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
  			msg = _("\"%s\" is not a table, composite type, or foreign table");
  			break;
+ 		case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE:
+ 			msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table");
+ 			break;
  		case ATT_VIEW:
  			msg = _("\"%s\" is not a view");
  			break;
***************
*** 4133,4139 **** find_composite_type_dependencies(Oid typeOid, Relation origRelation,
  		rel = relation_open(pg_depend->objid, AccessShareLock);
  		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
  
! 		if (rel->rd_rel->relkind == RELKIND_RELATION)
  		{
  			if (origTypeName)
  				ereport(ERROR,
--- 4215,4222 ----
  		rel = relation_open(pg_depend->objid, AccessShareLock);
  		att = rel->rd_att->attrs[pg_depend->objsubid - 1];
  
! 		if (rel->rd_rel->relkind == RELKIND_RELATION ||
! 			rel->rd_rel->relkind == RELKIND_MATVIEW)
  		{
  			if (origTypeName)
  				ereport(ERROR,
***************
*** 4959,4969 **** ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
  	 * allowSystemTableMods to be turned on.
  	 */
  	if (rel->rd_rel->relkind != RELKIND_RELATION &&
  		rel->rd_rel->relkind != RELKIND_INDEX &&
  		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table, index, or foreign table",
  						RelationGetRelationName(rel))));
  
  	/* Permissions checks */
--- 5042,5053 ----
  	 * allowSystemTableMods to be turned on.
  	 */
  	if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ 		rel->rd_rel->relkind != RELKIND_MATVIEW &&
  		rel->rd_rel->relkind != RELKIND_INDEX &&
  		rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
  						RelationGetRelationName(rel))));
  
  	/* Permissions checks */
***************
*** 8064,8069 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
--- 8148,8154 ----
  	{
  		case RELKIND_RELATION:
  		case RELKIND_VIEW:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_FOREIGN_TABLE:
  			/* ok to change owner */
  			break;
***************
*** 8220,8230 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
  							 tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
  
  		/*
! 		 * If we are operating on a table, also change the ownership of any
! 		 * indexes and sequences that belong to the table, as well as the
! 		 * table's toast table (if it has one)
  		 */
  		if (tuple_class->relkind == RELKIND_RELATION ||
  			tuple_class->relkind == RELKIND_TOASTVALUE)
  		{
  			List	   *index_oid_list;
--- 8305,8316 ----
  							 tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
  
  		/*
! 		 * If we are operating on a table or materialized view, also change
! 		 * the ownership of any indexes and sequences that belong to the
! 		 * relation, as well as its toast table (if it has one).
  		 */
  		if (tuple_class->relkind == RELKIND_RELATION ||
+ 			tuple_class->relkind == RELKIND_MATVIEW ||
  			tuple_class->relkind == RELKIND_TOASTVALUE)
  		{
  			List	   *index_oid_list;
***************
*** 8240,8246 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
  			list_free(index_oid_list);
  		}
  
! 		if (tuple_class->relkind == RELKIND_RELATION)
  		{
  			/* If it has a toast table, recurse to change its ownership */
  			if (tuple_class->reltoastrelid != InvalidOid)
--- 8326,8333 ----
  			list_free(index_oid_list);
  		}
  
! 		if (tuple_class->relkind == RELKIND_RELATION ||
! 			tuple_class->relkind == RELKIND_MATVIEW)
  		{
  			/* If it has a toast table, recurse to change its ownership */
  			if (tuple_class->reltoastrelid != InvalidOid)
***************
*** 8510,8515 **** ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
--- 8597,8603 ----
  		case RELKIND_RELATION:
  		case RELKIND_TOASTVALUE:
  		case RELKIND_VIEW:
+ 		case RELKIND_MATVIEW:
  			(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
  			break;
  		case RELKIND_INDEX:
***************
*** 8518,8524 **** ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
  		default:
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is not a table, index, or TOAST table",
  							RelationGetRelationName(rel))));
  			break;
  	}
--- 8606,8612 ----
  		default:
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table",
  							RelationGetRelationName(rel))));
  			break;
  	}
***************
*** 9785,9792 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt)
  }
  
  /*
!  * The guts of relocating a table to another namespace: besides moving
!  * the table itself, its dependent objects are relocated to the new schema.
   */
  void
  AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
--- 9873,9881 ----
  }
  
  /*
!  * The guts of relocating a table or materialized view to another namespace:
!  * besides moving the relation itself, its dependent objects are relocated to
!  * the new schema.
   */
  void
  AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
***************
*** 9807,9813 **** AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
  							   nspOid, false, false, objsMoved);
  
  	/* Fix other dependent stuff */
! 	if (rel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
  		AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
--- 9896,9903 ----
  							   nspOid, false, false, objsMoved);
  
  	/* Fix other dependent stuff */
! 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
! 		rel->rd_rel->relkind == RELKIND_MATVIEW)
  	{
  		AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
  		AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
***************
*** 10212,10221 **** AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
  
  /*
   * This is intended as a callback for RangeVarGetRelidExtended().  It allows
!  * the table to be locked only if (1) it's a plain table or TOAST table and
!  * (2) the current user is the owner (or the superuser).  This meets the
!  * permission-checking needs of both CLUSTER and REINDEX TABLE; we expose it
!  * here so that it can be used by both.
   */
  void
  RangeVarCallbackOwnsTable(const RangeVar *relation,
--- 10302,10312 ----
  
  /*
   * This is intended as a callback for RangeVarGetRelidExtended().  It allows
!  * the relation to be locked only if (1) it's a plain table, materialized
!  * view, or TOAST table and (2) the current user is the owner (or the
!  * superuser).  This meets the permission-checking needs of CLUSTER, REINDEX
!  * TABLE, and LOAD MATERIALIZED VIEW; we expose it here so that it can be used
!  * by all.
   */
  void
  RangeVarCallbackOwnsTable(const RangeVar *relation,
***************
*** 10235,10244 **** RangeVarCallbackOwnsTable(const RangeVar *relation,
  	relkind = get_rel_relkind(relId);
  	if (!relkind)
  		return;
! 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table", relation->relname)));
  
  	/* Check permissions */
  	if (!pg_class_ownercheck(relId, GetUserId()))
--- 10326,10336 ----
  	relkind = get_rel_relkind(relId);
  	if (!relkind)
  		return;
! 	if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
! 		relkind != RELKIND_MATVIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or materialized view", relation->relname)));
  
  	/* Check permissions */
  	if (!pg_class_ownercheck(relId, GetUserId()))
***************
*** 10320,10325 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10412,10422 ----
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("\"%s\" is not a view", rv->relname)));
  
+ 	if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is not a materialized view", rv->relname)));
+ 
  	if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 10356,10364 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
  	 * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
  	 * to a different schema, such as indexes and TOAST tables.
  	 */
! 	if (IsA(stmt, AlterObjectSchemaStmt) &&relkind != RELKIND_RELATION
! 		&& relkind != RELKIND_VIEW && relkind != RELKIND_SEQUENCE
! 		&& relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  			errmsg("\"%s\" is not a table, view, sequence, or foreign table",
--- 10453,10461 ----
  	 * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
  	 * to a different schema, such as indexes and TOAST tables.
  	 */
! 	if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION
! 		&& relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW
! 		&& relkind != RELKIND_SEQUENCE && relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  			errmsg("\"%s\" is not a table, view, sequence, or foreign table",
***************
*** 10366,10368 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10463,10513 ----
  
  	ReleaseSysCache(tuple);
  }
+ 
+ /*
+  * Returns true iff any relation underlying this query is a temporary database
+  * object (table, view, or materialized view).
+  *
+  */
+ bool
+ isQueryUsingTempRelation(Query *query)
+ {
+ 	return isQueryUsingTempRelation_walker((Node *) query, NULL);
+ }
+ 
+ static bool
+ isQueryUsingTempRelation_walker(Node *node, void *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	if (IsA(node, Query))
+ 	{
+ 		Query	   *query = (Query *) node;
+ 		ListCell   *rtable;
+ 
+ 		foreach(rtable, query->rtable)
+ 		{
+ 			RangeTblEntry *rte = lfirst(rtable);
+ 
+ 			if (rte->rtekind == RTE_RELATION)
+ 			{
+ 				Relation	rel = heap_open(rte->relid, AccessShareLock);
+ 				char		relpersistence = rel->rd_rel->relpersistence;
+ 
+ 				heap_close(rel, AccessShareLock);
+ 				if (relpersistence == RELPERSISTENCE_TEMP)
+ 					return true;
+ 			}
+ 		}
+ 
+ 		return query_tree_walker(query,
+ 								 isQueryUsingTempRelation_walker,
+ 								 context,
+ 								 QTW_IGNORE_JOINALIASES);
+ 	}
+ 
+ 	return expression_tree_walker(node,
+ 								  isQueryUsingTempRelation_walker,
+ 								  context);
+ }
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2746,2752 **** get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
  												 format_type_be(domainOid));
  
  			/* Otherwise we can ignore views, composite types, etc */
! 			if (rel->rd_rel->relkind != RELKIND_RELATION)
  			{
  				relation_close(rel, lockmode);
  				continue;
--- 2746,2753 ----
  												 format_type_be(domainOid));
  
  			/* Otherwise we can ignore views, composite types, etc */
! 			if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 				rel->rd_rel->relkind != RELKIND_MATVIEW)
  			{
  				relation_close(rel, lockmode);
  				continue;
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 340,362 **** get_rel_oids(Oid relid, const RangeVar *vacrel)
  	}
  	else
  	{
! 		/* Process all plain relations listed in pg_class */
  		Relation	pgclass;
  		HeapScanDesc scan;
  		HeapTuple	tuple;
- 		ScanKeyData key;
- 
- 		ScanKeyInit(&key,
- 					Anum_pg_class_relkind,
- 					BTEqualStrategyNumber, F_CHAREQ,
- 					CharGetDatum(RELKIND_RELATION));
  
  		pgclass = heap_open(RelationRelationId, AccessShareLock);
  
! 		scan = heap_beginscan(pgclass, SnapshotNow, 1, &key);
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
  			/* Make a relation list entry for this guy */
  			oldcontext = MemoryContextSwitchTo(vac_context);
  			oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
--- 340,365 ----
  	}
  	else
  	{
! 		/*
! 		 * Process all plain relations and materialized views listed in
! 		 * pg_class
! 		 */
  		Relation	pgclass;
  		HeapScanDesc scan;
  		HeapTuple	tuple;
  
  		pgclass = heap_open(RelationRelationId, AccessShareLock);
  
! 		scan = heap_beginscan(pgclass, SnapshotNow, 0, NULL);
  
  		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
  		{
+ 			Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+ 
+ 			if (classForm->relkind != RELKIND_RELATION &&
+ 				classForm->relkind != RELKIND_MATVIEW)
+ 				continue;
+ 
  			/* Make a relation list entry for this guy */
  			oldcontext = MemoryContextSwitchTo(vac_context);
  			oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
***************
*** 706,711 **** vac_update_datfrozenxid(void)
--- 709,715 ----
  		 * InvalidTransactionId in relfrozenxid anyway.)
  		 */
  		if (classForm->relkind != RELKIND_RELATION &&
+ 			classForm->relkind != RELKIND_MATVIEW &&
  			classForm->relkind != RELKIND_TOASTVALUE)
  			continue;
  
***************
*** 983,988 **** vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
--- 987,993 ----
  	 * relation.
  	 */
  	if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ 		onerel->rd_rel->relkind != RELKIND_MATVIEW &&
  		onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
  	{
  		ereport(WARNING,
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 36,92 ****
  
  
  static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
- static bool isViewOnTempTable_walker(Node *node, void *context);
- 
- /*---------------------------------------------------------------------
-  * isViewOnTempTable
-  *
-  * Returns true iff any of the relations underlying this view are
-  * temporary tables.
-  *---------------------------------------------------------------------
-  */
- static bool
- isViewOnTempTable(Query *viewParse)
- {
- 	return isViewOnTempTable_walker((Node *) viewParse, NULL);
- }
- 
- static bool
- isViewOnTempTable_walker(Node *node, void *context)
- {
- 	if (node == NULL)
- 		return false;
- 
- 	if (IsA(node, Query))
- 	{
- 		Query	   *query = (Query *) node;
- 		ListCell   *rtable;
- 
- 		foreach(rtable, query->rtable)
- 		{
- 			RangeTblEntry *rte = lfirst(rtable);
- 
- 			if (rte->rtekind == RTE_RELATION)
- 			{
- 				Relation	rel = heap_open(rte->relid, AccessShareLock);
- 				char		relpersistence = rel->rd_rel->relpersistence;
- 
- 				heap_close(rel, AccessShareLock);
- 				if (relpersistence == RELPERSISTENCE_TEMP)
- 					return true;
- 			}
- 		}
- 
- 		return query_tree_walker(query,
- 								 isViewOnTempTable_walker,
- 								 context,
- 								 QTW_IGNORE_JOINALIASES);
- 	}
- 
- 	return expression_tree_walker(node,
- 								  isViewOnTempTable_walker,
- 								  context);
- }
  
  /*---------------------------------------------------------------------
   * DefineVirtualRelation
--- 36,41 ----
***************
*** 506,512 **** DefineView(ViewStmt *stmt, const char *queryString)
  	 */
  	view = copyObject(stmt->view);		/* don't corrupt original command */
  	if (view->relpersistence == RELPERSISTENCE_PERMANENT
! 		&& isViewOnTempTable(viewParse))
  	{
  		view->relpersistence = RELPERSISTENCE_TEMP;
  		ereport(NOTICE,
--- 455,461 ----
  	 */
  	view = copyObject(stmt->view);		/* don't corrupt original command */
  	if (view->relpersistence == RELPERSISTENCE_PERMANENT
! 		&& isQueryUsingTempRelation(viewParse))
  	{
  		view->relpersistence = RELPERSISTENCE_TEMP;
  		ereport(NOTICE,
***************
*** 530,535 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 479,493 ----
  	 */
  	CommandCounterIncrement();
  
+ 	StoreViewQuery(viewOid, viewParse, stmt->replace);
+ }
+ 
+ /*
+  * Use the rules system to store the query for the view.
+  */
+ void
+ StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
+ {
  	/*
  	 * The range table of 'viewParse' does not contain entries for the "OLD"
  	 * and "NEW" relations. So... add them!
***************
*** 539,543 **** DefineView(ViewStmt *stmt, const char *queryString)
  	/*
  	 * Now create the rules associated with the view.
  	 */
! 	DefineViewRules(viewOid, viewParse, stmt->replace);
  }
--- 497,501 ----
  	/*
  	 * Now create the rules associated with the view.
  	 */
! 	DefineViewRules(viewOid, viewParse, replace);
  }
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 490,495 **** ExecutorRewind(QueryDesc *queryDesc)
--- 490,532 ----
  
  
  /*
+  * ExecCheckRelationsValid
+  *		Check that relations which are to be accessed are flagged as valid.
+  *
+  * If not, throw error. For a materialized view, suggest refresh.
+  */
+ static void
+ ExecCheckRelationsValid(List *rangeTable)
+ {
+ /*
+ 	ListCell   *l;
+ 
+ 	foreach(l, rangeTable)
+ 	{
+ 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ 
+ 		if (rte->rtekind != RTE_RELATION)
+ 			continue;
+ 
+ 		if (!RelationIsFlaggedAsValid(rte->relid))
+ 		{
+ 			if (rte->relkind == RELKIND_MATVIEW)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 						 errmsg("materialized view \"%s\" has not been populated",
+ 								get_rel_name(rte->relid)),
+ 						 errhint("Use the LOAD MATERIALIZED VIEW command.")));
+ 			else
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ 						 errmsg("relation \"%s\" is flagged as invalid",
+ 								get_rel_name(rte->relid))));
+ 		}
+ 	}
+ */
+ }
+ 
+ /*
   * ExecCheckRTPerms
   *		Check access permissions for all relations listed in a range table.
   *
***************
*** 724,729 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 761,771 ----
  	ExecCheckRTPerms(rangeTable, true);
  
  	/*
+ 	 * Ensure that all referrenced relations are flagged as valid.
+ 	 */
+ 	ExecCheckRelationsValid(rangeTable);
+ 
+ 	/*
  	 * initialize the node's execution state
  	 */
  	estate->es_range_table = rangeTable;
***************
*** 984,989 **** CheckValidResultRel(Relation resultRel, CmdType operation)
--- 1026,1037 ----
  					break;
  			}
  			break;
+ 		case RELKIND_MATVIEW:
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("cannot change materialized view \"%s\"",
+ 							RelationGetRelationName(resultRel))));
+ 			break;
  		case RELKIND_FOREIGN_TABLE:
  			ereport(ERROR,
  					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 1034,1039 **** CheckValidRowMarkRel(Relation rel, RowMarkType markType)
--- 1082,1094 ----
  					 errmsg("cannot lock rows in view \"%s\"",
  							RelationGetRelationName(rel))));
  			break;
+ 		case RELKIND_MATVIEW:
+ 			/* Should not get here */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 					 errmsg("cannot lock rows in materialized view \"%s\"",
+ 							RelationGetRelationName(rel))));
+ 			break;
  		case RELKIND_FOREIGN_TABLE:
  			/* Perhaps we can support this someday, but not today */
  			ereport(ERROR,
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
***************
*** 1941,1946 **** _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
--- 1941,1953 ----
  					if (((CreateTableAsStmt *) stmt)->is_select_into)
  						res = SPI_OK_SELINTO;
  				}
+ 				else if (IsA(stmt, LoadMatViewStmt))
+ 				{
+ 					Assert(strncmp(completionTag,
+ 								   "LOAD MATERIALIZED VIEW ", 23) == 0);
+ 					_SPI_current->processed = strtoul(completionTag + 23,
+ 													  NULL, 10);
+ 				}
  				else if (IsA(stmt, CopyStmt))
  				{
  					Assert(strncmp(completionTag, "COPY ", 5) == 0);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1032,1037 **** _copyIntoClause(const IntoClause *from)
--- 1032,1038 ----
  	COPY_SCALAR_FIELD(onCommit);
  	COPY_STRING_FIELD(tableSpaceName);
  	COPY_SCALAR_FIELD(skipData);
+ 	COPY_SCALAR_FIELD(relkind);
  
  	return newnode;
  }
***************
*** 3226,3236 **** _copyCreateTableAsStmt(const CreateTableAsStmt *from)
--- 3227,3248 ----
  
  	COPY_NODE_FIELD(query);
  	COPY_NODE_FIELD(into);
+ 	COPY_SCALAR_FIELD(relkind);
  	COPY_SCALAR_FIELD(is_select_into);
  
  	return newnode;
  }
  
+ static LoadMatViewStmt *
+ _copyLoadMatViewStmt(const LoadMatViewStmt *from)
+ {
+ 	LoadMatViewStmt *newnode = makeNode(LoadMatViewStmt);
+ 
+ 	COPY_NODE_FIELD(relation);
+ 
+ 	return newnode;
+ }
+ 
  static CreateSeqStmt *
  _copyCreateSeqStmt(const CreateSeqStmt *from)
  {
***************
*** 4301,4306 **** copyObject(const void *from)
--- 4313,4321 ----
  		case T_CreateTableAsStmt:
  			retval = _copyCreateTableAsStmt(from);
  			break;
+ 		case T_LoadMatViewStmt:
+ 			retval = _copyLoadMatViewStmt(from);
+ 			break;
  		case T_CreateSeqStmt:
  			retval = _copyCreateSeqStmt(from);
  			break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 124,129 **** _equalIntoClause(const IntoClause *a, const IntoClause *b)
--- 124,130 ----
  	COMPARE_SCALAR_FIELD(onCommit);
  	COMPARE_STRING_FIELD(tableSpaceName);
  	COMPARE_SCALAR_FIELD(skipData);
+ 	COMPARE_SCALAR_FIELD(relkind);
  
  	return true;
  }
***************
*** 1523,1534 **** _equalCreateTableAsStmt(const CreateTableAsStmt *a, const CreateTableAsStmt *b)
--- 1524,1544 ----
  {
  	COMPARE_NODE_FIELD(query);
  	COMPARE_NODE_FIELD(into);
+ 	COMPARE_SCALAR_FIELD(relkind);
  	COMPARE_SCALAR_FIELD(is_select_into);
  
  	return true;
  }
  
  static bool
+ _equalLoadMatViewStmt(const LoadMatViewStmt *a, const LoadMatViewStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(relation);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
  {
  	COMPARE_NODE_FIELD(sequence);
***************
*** 2788,2793 **** equal(const void *a, const void *b)
--- 2798,2806 ----
  		case T_CreateTableAsStmt:
  			retval = _equalCreateTableAsStmt(a, b);
  			break;
+ 		case T_LoadMatViewStmt:
+ 			retval = _equalLoadMatViewStmt(a, b);
+ 			break;
  		case T_CreateSeqStmt:
  			retval = _equalCreateSeqStmt(a, b);
  			break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 893,898 **** _outIntoClause(StringInfo str, const IntoClause *node)
--- 893,899 ----
  	WRITE_ENUM_FIELD(onCommit, OnCommitAction);
  	WRITE_STRING_FIELD(tableSpaceName);
  	WRITE_BOOL_FIELD(skipData);
+ 	WRITE_CHAR_FIELD(relkind);
  }
  
  static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 395,400 **** _readIntoClause(void)
--- 395,401 ----
  	READ_ENUM_FIELD(onCommit, OnCommitAction);
  	READ_STRING_FIELD(tableSpaceName);
  	READ_BOOL_FIELD(skipData);
+ 	READ_CHAR_FIELD(relkind);
  
  	READ_DONE();
  }
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 3375,3381 **** plan_cluster_use_sort(Oid tableOid, Oid indexOid)
  	rte = makeNode(RangeTblEntry);
  	rte->rtekind = RTE_RELATION;
  	rte->relid = tableOid;
! 	rte->relkind = RELKIND_RELATION;
  	rte->lateral = false;
  	rte->inh = false;
  	rte->inFromCl = true;
--- 3375,3381 ----
  	rte = makeNode(RangeTblEntry);
  	rte->rtekind = RTE_RELATION;
  	rte->relid = tableOid;
! 	rte->relkind = RELKIND_RELATION;  /* Don't be too picky. */
  	rte->lateral = false;
  	rte->inh = false;
  	rte->inFromCl = true;
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 396,401 **** estimate_rel_size(Relation rel, int32 *attr_widths,
--- 396,402 ----
  	{
  		case RELKIND_RELATION:
  		case RELKIND_INDEX:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_TOASTVALUE:
  			/* it has storage, ok to call the smgr */
  			curpages = RelationGetNumberOfBlocks(rel);
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 190,195 **** transformTopLevelStmt(ParseState *pstate, Node *parseTree)
--- 190,196 ----
  
  			ctas->query = parseTree;
  			ctas->into = stmt->intoClause;
+ 			ctas->relkind = OBJECT_TABLE;
  			ctas->is_select_into = true;
  
  			/*
***************
*** 324,329 **** analyze_requires_snapshot(Node *parseTree)
--- 325,335 ----
  			result = true;
  			break;
  
+ 		case T_LoadMatViewStmt:
+ 			/* yes, because the SELECT from pg_rewrite must be analyzed */
+ 			result = true;
+ 			break;
+ 
  		default:
  			/* other utility statements don't have any real parse analysis */
  			result = false;
***************
*** 2117,2123 **** transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
  
  /*
   * transformCreateTableAsStmt -
!  *	transform a CREATE TABLE AS (or SELECT ... INTO) Statement
   *
   * As with EXPLAIN, transform the contained statement now.
   */
--- 2123,2130 ----
  
  /*
   * transformCreateTableAsStmt -
!  *	transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW
!  *  Statement
   *
   * As with EXPLAIN, transform the contained statement now.
   */
***************
*** 2126,2131 **** transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
--- 2133,2156 ----
  {
  	Query	   *result;
  
+ 	/*
+ 	 * Set relkind in IntoClause based on statement relkind.  These are
+ 	 * different types, because the parser users the ObjectType enumeration
+ 	 * and the executor uses RELKIND_* defines.
+ 	 */
+ 	switch (stmt->relkind)
+ 	{
+ 		case (OBJECT_TABLE):
+ 			stmt->into->relkind = RELKIND_RELATION;
+ 			break;
+ 		case (OBJECT_MATVIEW):
+ 			stmt->into->relkind = RELKIND_MATVIEW;
+ 			break;
+ 		default:
+ 			elog(ERROR, "unrecognized object relkind: %d",
+ 				 (int) stmt->relkind);
+ 	}
+ 
  	/* transform contained query */
  	stmt->query = (Node *) transformStmt(pstate, stmt->query);
  
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 121,126 **** typedef struct PrivTarget
--- 121,133 ----
  #define CAS_NOT_VALID				0x10
  #define CAS_NO_INHERIT				0x20
  
+ /*
+  * In the IntoClause structure there is a char value which will eventually be
+  * set to RELKIND_RELATION or RELKIND_MATVIEW based on the relkind field in
+  * the statement-level structure, which is an ObjectType. Define the default
+  * here, which should always be overridden later.
+  */
+ #define INTO_CLAUSE_RELKIND_DEFAULT	'\0'
  
  #define parser_yyerror(msg)  scanner_yyerror(msg, yyscanner)
  #define parser_errposition(pos)  scanner_errposition(pos, yyscanner)
***************
*** 247,252 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 254,260 ----
  		DeallocateStmt PrepareStmt ExecuteStmt
  		DropOwnedStmt ReassignOwnedStmt
  		AlterTSConfigurationStmt AlterTSDictionaryStmt
+ 		CreateMatViewStmt LoadMatViewStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 555,561 **** static void processCASbits(int cas_bits, int location, const char *constrType,
  	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
  	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
  
! 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
  	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
  	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
--- 563,569 ----
  	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
  	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
  
! 	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
  	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
  	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
***************
*** 743,748 **** stmt :
--- 751,757 ----
  			| CreateForeignTableStmt
  			| CreateFunctionStmt
  			| CreateGroupStmt
+ 			| CreateMatViewStmt
  			| CreateOpClassStmt
  			| CreateOpFamilyStmt
  			| AlterOpFamilyStmt
***************
*** 788,793 **** stmt :
--- 797,803 ----
  			| IndexStmt
  			| InsertStmt
  			| ListenStmt
+ 			| LoadMatViewStmt
  			| LoadStmt
  			| LockStmt
  			| NotifyStmt
***************
*** 1694,1702 **** DiscardStmt:
  
  /*****************************************************************************
   *
!  *	ALTER [ TABLE | INDEX | SEQUENCE | VIEW ] variations
   *
!  * Note: we accept all subcommands for each of the four variants, and sort
   * out what's really legal at execution time.
   *****************************************************************************/
  
--- 1704,1712 ----
  
  /*****************************************************************************
   *
!  *	ALTER [ TABLE | INDEX | SEQUENCE | VIEW | MATERIALIZED VIEW ] variations
   *
!  * Note: we accept all subcommands for each of the five variants, and sort
   * out what's really legal at execution time.
   *****************************************************************************/
  
***************
*** 1773,1778 **** AlterTableStmt:
--- 1783,1806 ----
  					n->missing_ok = true;
  					$$ = (Node *)n;
  				}
+ 		|	ALTER MATERIALIZED VIEW qualified_name alter_table_cmds
+ 				{
+ 					AlterTableStmt *n = makeNode(AlterTableStmt);
+ 					n->relation = $4;
+ 					n->cmds = $5;
+ 					n->relkind = OBJECT_MATVIEW;
+ 					n->missing_ok = false;
+ 					$$ = (Node *)n;
+ 				}
+ 		|	ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name alter_table_cmds
+ 				{
+ 					AlterTableStmt *n = makeNode(AlterTableStmt);
+ 					n->relation = $6;
+ 					n->cmds = $7;
+ 					n->relkind = OBJECT_MATVIEW;
+ 					n->missing_ok = true;
+ 					$$ = (Node *)n;
+ 				}
  		;
  
  alter_table_cmds:
***************
*** 3149,3154 **** CreateAsStmt:
--- 3177,3183 ----
  					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
  					ctas->query = $6;
  					ctas->into = $4;
+ 					ctas->relkind = OBJECT_TABLE;
  					ctas->is_select_into = false;
  					/* cram additional flags into the IntoClause */
  					$4->rel->relpersistence = $2;
***************
*** 3167,3172 **** create_as_target:
--- 3196,3202 ----
  					$$->onCommit = $4;
  					$$->tableSpaceName = $5;
  					$$->skipData = false;		/* might get changed later */
+ 					$$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
  				}
  		;
  
***************
*** 3180,3185 **** opt_with_data:
--- 3210,3255 ----
  /*****************************************************************************
   *
   *		QUERY :
+  *				CREATE MATERIALIZED VIEW relname AS SelectStmt
+  *
+  *****************************************************************************/
+ 
+ CreateMatViewStmt:
+ 		CREATE OptTemp MATERIALIZED VIEW create_as_target AS SelectStmt opt_with_data
+ 				{
+ 					CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+ 					ctas->query = $7;
+ 					ctas->into = $5;
+ 					ctas->relkind = OBJECT_MATVIEW;
+ 					ctas->is_select_into = false;
+ 					/* cram additional flags into the IntoClause */
+ 					$5->rel->relpersistence = $2;
+ 					$5->skipData = !($8);
+ 					$$ = (Node *) ctas;
+ 				}
+ 		;
+ 
+ 
+ /*****************************************************************************
+  *
+  *		QUERY :
+  *				LOAD MATERIALIZED VIEW qualified_name
+  *
+  *****************************************************************************/
+ 
+ LoadMatViewStmt:
+ 		LOAD MATERIALIZED VIEW qualified_name
+ 				{
+ 					LoadMatViewStmt *n = makeNode(LoadMatViewStmt);
+ 					n->relation = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ 
+ /*****************************************************************************
+  *
+  *		QUERY :
   *				CREATE SEQUENCE seqname
   *				ALTER SEQUENCE seqname
   *
***************
*** 3694,3699 **** AlterExtensionContentsStmt:
--- 3764,3778 ----
  					n->objname = $6;
  					$$ = (Node *)n;
  				}
+ 			| ALTER EXTENSION name add_drop MATERIALIZED VIEW any_name
+ 				{
+ 					AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ 					n->extname = $3;
+ 					n->action = $4;
+ 					n->objtype = OBJECT_MATVIEW;
+ 					n->objname = $7;
+ 					$$ = (Node *)n;
+ 				}
  			| ALTER EXTENSION name add_drop FOREIGN TABLE any_name
  				{
  					AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
***************
*** 5020,5025 **** DropStmt:	DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior
--- 5099,5105 ----
  drop_type:	TABLE									{ $$ = OBJECT_TABLE; }
  			| SEQUENCE								{ $$ = OBJECT_SEQUENCE; }
  			| VIEW									{ $$ = OBJECT_VIEW; }
+ 			| MATERIALIZED VIEW						{ $$ = OBJECT_MATVIEW; }
  			| INDEX									{ $$ = OBJECT_INDEX; }
  			| FOREIGN TABLE							{ $$ = OBJECT_FOREIGN_TABLE; }
  			| EVENT TRIGGER 						{ $$ = OBJECT_EVENT_TRIGGER; }
***************
*** 5086,5092 **** opt_restart_seqs:
   *				   EXTENSION | ROLE | TEXT SEARCH PARSER |
   *				   TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
   *				   TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
!  *				   FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER ] <objname> |
   *				 AGGREGATE <aggname> (arg1, ...) |
   *				 FUNCTION <funcname> (arg1, arg2, ...) |
   *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
--- 5166,5173 ----
   *				   EXTENSION | ROLE | TEXT SEARCH PARSER |
   *				   TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
   *				   TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
!  *				   FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER |
!  *				   MATERIALIZED VIEW] <objname> |
   *				 AGGREGATE <aggname> (arg1, ...) |
   *				 FUNCTION <funcname> (arg1, arg2, ...) |
   *				 OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
***************
*** 5260,5265 **** comment_type:
--- 5341,5347 ----
  			| DOMAIN_P							{ $$ = OBJECT_DOMAIN; }
  			| TYPE_P							{ $$ = OBJECT_TYPE; }
  			| VIEW								{ $$ = OBJECT_VIEW; }
+ 			| MATERIALIZED VIEW					{ $$ = OBJECT_MATVIEW; }
  			| COLLATION							{ $$ = OBJECT_COLLATION; }
  			| CONVERSION_P						{ $$ = OBJECT_CONVERSION; }
  			| TABLESPACE						{ $$ = OBJECT_TABLESPACE; }
***************
*** 5361,5366 **** security_label_type:
--- 5443,5449 ----
  			| TABLESPACE						{ $$ = OBJECT_TABLESPACE; }
  			| TYPE_P							{ $$ = OBJECT_TYPE; }
  			| VIEW								{ $$ = OBJECT_VIEW; }
+ 			| MATERIALIZED VIEW					{ $$ = OBJECT_MATVIEW; }
  		;
  
  security_label:	Sconst				{ $$ = $1; }
***************
*** 6903,6908 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 6986,7011 ----
  					n->missing_ok = true;
  					$$ = (Node *)n;
  				}
+ 			| ALTER MATERIALIZED VIEW qualified_name RENAME TO name
+ 				{
+ 					RenameStmt *n = makeNode(RenameStmt);
+ 					n->renameType = OBJECT_MATVIEW;
+ 					n->relation = $4;
+ 					n->subname = NULL;
+ 					n->newname = $7;
+ 					n->missing_ok = false;
+ 					$$ = (Node *)n;
+ 				}
+ 			| ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME TO name
+ 				{
+ 					RenameStmt *n = makeNode(RenameStmt);
+ 					n->renameType = OBJECT_MATVIEW;
+ 					n->relation = $6;
+ 					n->subname = NULL;
+ 					n->newname = $9;
+ 					n->missing_ok = true;
+ 					$$ = (Node *)n;
+ 				}
  			| ALTER INDEX qualified_name RENAME TO name
  				{
  					RenameStmt *n = makeNode(RenameStmt);
***************
*** 7310,7315 **** AlterObjectSchemaStmt:
--- 7413,7436 ----
  					n->missing_ok = true;
  					$$ = (Node *)n;
  				}
+ 			| ALTER MATERIALIZED VIEW qualified_name SET SCHEMA name
+ 				{
+ 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ 					n->objectType = OBJECT_MATVIEW;
+ 					n->relation = $4;
+ 					n->newschema = $7;
+ 					n->missing_ok = false;
+ 					$$ = (Node *)n;
+ 				}
+ 			| ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name SET SCHEMA name
+ 				{
+ 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ 					n->objectType = OBJECT_MATVIEW;
+ 					n->relation = $6;
+ 					n->newschema = $9;
+ 					n->missing_ok = true;
+ 					$$ = (Node *)n;
+ 				}
  			| ALTER FOREIGN TABLE relation_expr SET SCHEMA name
  				{
  					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
***************
*** 8464,8469 **** ExplainableStmt:
--- 8585,8592 ----
  			| DeleteStmt
  			| DeclareCursorStmt
  			| CreateAsStmt
+ 			| CreateMatViewStmt
+ 			| LoadMatViewStmt
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
***************
*** 8548,8553 **** ExecuteStmt: EXECUTE name execute_param_clause
--- 8671,8677 ----
  					n->params = $8;
  					ctas->query = (Node *) n;
  					ctas->into = $4;
+ 					ctas->relkind = OBJECT_TABLE;
  					ctas->is_select_into = false;
  					/* cram additional flags into the IntoClause */
  					$4->rel->relpersistence = $2;
***************
*** 9094,9099 **** into_clause:
--- 9218,9224 ----
  					$$->onCommit = ONCOMMIT_NOOP;
  					$$->tableSpaceName = NULL;
  					$$->skipData = false;
+ 					$$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
  				}
  			| /*EMPTY*/
  				{ $$ = NULL; }
***************
*** 12558,12563 **** unreserved_keyword:
--- 12683,12689 ----
  			| LOCK_P
  			| MAPPING
  			| MATCH
+ 			| MATERIALIZED
  			| MAXVALUE
  			| MINUTE_P
  			| MINVALUE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 646,651 **** transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
--- 646,652 ----
  
  	if (relation->rd_rel->relkind != RELKIND_RELATION &&
  		relation->rd_rel->relkind != RELKIND_VIEW &&
+ 		relation->rd_rel->relkind != RELKIND_MATVIEW &&
  		relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
  		relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
*** a/src/backend/postmaster/autovacuum.c
--- b/src/backend/postmaster/autovacuum.c
***************
*** 1962,1983 **** do_autovacuum(void)
  	 * Scan pg_class to determine which tables to vacuum.
  	 *
  	 * We do this in two passes: on the first one we collect the list of plain
! 	 * relations, and on the second one we collect TOAST tables. The reason
! 	 * for doing the second pass is that during it we want to use the main
! 	 * relation's pg_class.reloptions entry if the TOAST table does not have
! 	 * any, and we cannot obtain it unless we know beforehand what's the main
! 	 * table OID.
  	 *
  	 * We need to check TOAST tables separately because in cases with short,
  	 * wide tables there might be proportionally much more activity in the
  	 * TOAST table than in its parent.
  	 */
! 	ScanKeyInit(&key,
! 				Anum_pg_class_relkind,
! 				BTEqualStrategyNumber, F_CHAREQ,
! 				CharGetDatum(RELKIND_RELATION));
! 
! 	relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
  
  	/*
  	 * On the first pass, we collect main tables to vacuum, and also the main
--- 1962,1978 ----
  	 * Scan pg_class to determine which tables to vacuum.
  	 *
  	 * We do this in two passes: on the first one we collect the list of plain
! 	 * relations and materialized views, and on the second one we collect
! 	 * TOAST tables. The reason for doing the second pass is that during it we
! 	 * want to use the main relation's pg_class.reloptions entry if the TOAST
! 	 * table does not have any, and we cannot obtain it unless we know
! 	 * beforehand what's the main  table OID.
  	 *
  	 * We need to check TOAST tables separately because in cases with short,
  	 * wide tables there might be proportionally much more activity in the
  	 * TOAST table than in its parent.
  	 */
! 	relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL);
  
  	/*
  	 * On the first pass, we collect main tables to vacuum, and also the main
***************
*** 1993,1998 **** do_autovacuum(void)
--- 1988,1997 ----
  		bool		doanalyze;
  		bool		wraparound;
  
+ 		if (classForm->relkind != RELKIND_RELATION &&
+ 			classForm->relkind != RELKIND_MATVIEW)
+ 			continue;
+ 
  		relid = HeapTupleGetOid(tuple);
  
  		/* Fetch reloptions and the pgstat entry for this table */
***************
*** 2378,2383 **** extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
--- 2377,2383 ----
  	AutoVacOpts *av;
  
  	Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
+ 		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
  		   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
  
  	relopts = extractRelOptions(tup, pg_class_desc, InvalidOid);
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
***************
*** 1561,1566 **** pgstat_initstats(Relation rel)
--- 1561,1567 ----
  
  	/* We only count stats for things that have storage */
  	if (!(relkind == RELKIND_RELATION ||
+ 		  relkind == RELKIND_MATVIEW ||
  		  relkind == RELKIND_INDEX ||
  		  relkind == RELKIND_TOASTVALUE ||
  		  relkind == RELKIND_SEQUENCE))
*** a/src/backend/rewrite/rewriteDefine.c
--- b/src/backend/rewrite/rewriteDefine.c
***************
*** 50,56 **** static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
   *	  takes the arguments and inserts them as a row into the system
   *	  relation "pg_rewrite"
   */
! static Oid
  InsertRule(char *rulname,
  		   int evtype,
  		   Oid eventrel_oid,
--- 50,56 ----
   *	  takes the arguments and inserts them as a row into the system
   *	  relation "pg_rewrite"
   */
! static void
  InsertRule(char *rulname,
  		   int evtype,
  		   Oid eventrel_oid,
***************
*** 183,190 **** InsertRule(char *rulname,
  						   RewriteRelationId, rewriteObjectId, 0, NULL);
  
  	heap_close(pg_rewrite_desc, RowExclusiveLock);
- 
- 	return rewriteObjectId;
  }
  
  /*
--- 183,188 ----
***************
*** 256,261 **** DefineQueryRewrite(char *rulename,
--- 254,260 ----
  	 * Verify relation is of a type that rules can sensibly be applied to.
  	 */
  	if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ 		event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
  		event_relation->rd_rel->relkind != RELKIND_VIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 352,358 **** DefineQueryRewrite(char *rulename,
  		 */
  		checkRuleResultList(query->targetList,
  							RelationGetDescr(event_relation),
! 							true);
  
  		/*
  		 * ... there must not be another ON SELECT rule already ...
--- 351,358 ----
  		 */
  		checkRuleResultList(query->targetList,
  							RelationGetDescr(event_relation),
! 							event_relation->rd_rel->relkind !=
! 								RELKIND_MATVIEW);
  
  		/*
  		 * ... there must not be another ON SELECT rule already ...
***************
*** 410,416 **** DefineQueryRewrite(char *rulename,
  		 * business of converting relations to views is just a kluge to allow
  		 * loading ancient pg_dump files.)
  		 */
! 		if (event_relation->rd_rel->relkind != RELKIND_VIEW)
  		{
  			HeapScanDesc scanDesc;
  
--- 410,417 ----
  		 * business of converting relations to views is just a kluge to allow
  		 * loading ancient pg_dump files.)
  		 */
! 		if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
! 			event_relation->rd_rel->relkind != RELKIND_MATVIEW)
  		{
  			HeapScanDesc scanDesc;
  
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1168,1174 **** rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
  	const char *attrname;
  	TargetEntry *tle;
  
! 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
  	{
  		/*
  		 * Emit CTID so that executor can find the row to update or delete.
--- 1168,1175 ----
  	const char *attrname;
  	TargetEntry *tle;
  
! 	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
! 		target_relation->rd_rel->relkind == RELKIND_MATVIEW)
  	{
  		/*
  		 * Emit CTID so that executor can find the row to update or delete.
***************
*** 1591,1596 **** fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
--- 1592,1607 ----
  		rel = heap_open(rte->relid, NoLock);
  
  		/*
+ 		 * Skip materialized view expansion when resultRelation is set.
+ 		 */
+ 		if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
+ 			rel->rd_rel->relisvalid)
+ 		{
+ 			heap_close(rel, NoLock);
+ 			break;
+ 		}
+ 
+ 		/*
  		 * Collect the RIR rules that we must apply
  		 */
  		rules = rel->rd_rules;
*** a/src/backend/storage/lmgr/predicate.c
--- b/src/backend/storage/lmgr/predicate.c
***************
*** 460,472 **** static void OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *read
  
  /*
   * Does this relation participate in predicate locking? Temporary and system
!  * relations are exempt.
   */
  static inline bool
  PredicateLockingNeededForRelation(Relation relation)
  {
  	return !(relation->rd_id < FirstBootstrapObjectId ||
! 			 RelationUsesLocalBuffers(relation));
  }
  
  /*
--- 460,473 ----
  
  /*
   * Does this relation participate in predicate locking? Temporary and system
!  * relations are exempt, as are materialized views.
   */
  static inline bool
  PredicateLockingNeededForRelation(Relation relation)
  {
  	return !(relation->rd_id < FirstBootstrapObjectId ||
! 			 RelationUsesLocalBuffers(relation) ||
! 			 relation->rd_rel->relkind == RELKIND_MATVIEW);
  }
  
  /*
*** a/src/backend/tcop/dest.c
--- b/src/backend/tcop/dest.c
***************
*** 32,37 ****
--- 32,38 ----
  #include "access/xact.h"
  #include "commands/copy.h"
  #include "commands/createas.h"
+ #include "commands/matview.h"
  #include "executor/functions.h"
  #include "executor/tstoreReceiver.h"
  #include "libpq/libpq.h"
***************
*** 125,130 **** CreateDestReceiver(CommandDest dest)
--- 126,134 ----
  
  		case DestSQLFunction:
  			return CreateSQLFunctionDestReceiver();
+ 
+ 		case DestTransientRel:
+ 			return CreateTransientRelDestReceiver(InvalidOid);
  	}
  
  	/* should never get here */
***************
*** 157,162 **** EndCommand(const char *commandTag, CommandDest dest)
--- 161,167 ----
  		case DestIntoRel:
  		case DestCopyOut:
  		case DestSQLFunction:
+ 		case DestTransientRel:
  			break;
  	}
  }
***************
*** 198,203 **** NullCommand(CommandDest dest)
--- 203,209 ----
  		case DestIntoRel:
  		case DestCopyOut:
  		case DestSQLFunction:
+ 		case DestTransientRel:
  			break;
  	}
  }
***************
*** 241,246 **** ReadyForQuery(CommandDest dest)
--- 247,253 ----
  		case DestIntoRel:
  		case DestCopyOut:
  		case DestSQLFunction:
+ 		case DestTransientRel:
  			break;
  	}
  }
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 37,42 ****
--- 37,43 ----
  #include "commands/event_trigger.h"
  #include "commands/explain.h"
  #include "commands/extension.h"
+ #include "commands/matview.h"
  #include "commands/lockcmds.h"
  #include "commands/portalcmds.h"
  #include "commands/prepare.h"
***************
*** 110,115 **** CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
--- 111,137 ----
  	ReleaseSysCache(tuple);
  }
  
+ /*
+  * Tells whether a relation is flagged as valid. The caller must be holding a
+  * lock on the relation.
+  */
+ bool
+ RelationIsFlaggedAsValid(Oid relid)
+ {
+ 	HeapTuple	tuple;
+ 	Form_pg_class classtup;
+ 	bool		result;
+ 
+ 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		return false;					/* concurrently dropped */
+ 	classtup = (Form_pg_class) GETSTRUCT(tuple);
+ 	result = classtup->relisvalid;
+ 	ReleaseSysCache(tuple);
+ 
+ 	return result;
+ }
+ 
  
  /*
   * CommandIsReadOnly: is an executable query read-only?
***************
*** 202,207 **** check_xact_readonly(Node *parsetree)
--- 224,230 ----
  		case T_CreateSeqStmt:
  		case T_CreateStmt:
  		case T_CreateTableAsStmt:
+ 		case T_LoadMatViewStmt:
  		case T_CreateTableSpaceStmt:
  		case T_CreateTrigStmt:
  		case T_CompositeTypeStmt:
***************
*** 682,687 **** standard_ProcessUtility(Node *parsetree,
--- 705,711 ----
  					case OBJECT_TABLE:
  					case OBJECT_SEQUENCE:
  					case OBJECT_VIEW:
+ 					case OBJECT_MATVIEW:
  					case OBJECT_FOREIGN_TABLE:
  						RemoveRelations((DropStmt *) parsetree);
  						break;
***************
*** 1144,1149 **** standard_ProcessUtility(Node *parsetree,
--- 1168,1180 ----
  							  queryString, params, completionTag);
  			break;
  
+ 		case T_LoadMatViewStmt:
+ 			if (isCompleteQuery)
+ 				EventTriggerDDLCommandStart(parsetree);
+ 			ExecLoadMatView((LoadMatViewStmt *) parsetree,
+ 							  queryString, params, completionTag);
+ 			break;
+ 
  		case T_VariableSetStmt:
  			ExecSetVariableStmt((VariableSetStmt *) parsetree);
  			break;
***************
*** 1270,1275 **** standard_ProcessUtility(Node *parsetree,
--- 1301,1307 ----
  						ReindexIndex(stmt->relation);
  						break;
  					case OBJECT_TABLE:
+ 					case OBJECT_MATVIEW:
  						ReindexTable(stmt->relation);
  						break;
  					case OBJECT_DATABASE:
***************
*** 1489,1497 **** QueryReturnsTuples(Query *parsetree)
   * We assume it is invoked only on already-parse-analyzed statements
   * (else the contained parsetree isn't a Query yet).
   *
!  * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO),
!  * potentially Query-containing utility statements can be nested.  This
!  * function will drill down to a non-utility Query, or return NULL if none.
   */
  Query *
  UtilityContainsQuery(Node *parsetree)
--- 1521,1530 ----
   * We assume it is invoked only on already-parse-analyzed statements
   * (else the contained parsetree isn't a Query yet).
   *
!  * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO and
!  * CREATE MATERIALIZED VIEW), potentially Query-containing utility statements
!  * can be nested.  This function will drill down to a non-utility Query, or
!  * return NULL if none.
   */
  Query *
  UtilityContainsQuery(Node *parsetree)
***************
*** 1635,1640 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1668,1676 ----
  		case OBJECT_VIEW:
  			tag = "ALTER VIEW";
  			break;
+ 		case OBJECT_MATVIEW:
+ 			tag = "ALTER MATERIALIZED VIEW";
+ 			break;
  		default:
  			tag = "???";
  			break;
***************
*** 1832,1837 **** CreateCommandTag(Node *parsetree)
--- 1868,1876 ----
  				case OBJECT_VIEW:
  					tag = "DROP VIEW";
  					break;
+ 				case OBJECT_MATVIEW:
+ 					tag = "DROP MATERIALIZED VIEW";
+ 					break;
  				case OBJECT_INDEX:
  					tag = "DROP INDEX";
  					break;
***************
*** 2093,2102 **** CreateCommandTag(Node *parsetree)
  			break;
  
  		case T_CreateTableAsStmt:
! 			if (((CreateTableAsStmt *) parsetree)->is_select_into)
! 				tag = "SELECT INTO";
! 			else
! 				tag = "CREATE TABLE AS";
  			break;
  
  		case T_VariableSetStmt:
--- 2132,2155 ----
  			break;
  
  		case T_CreateTableAsStmt:
! 			switch (((CreateTableAsStmt *) parsetree)->relkind)
! 			{
! 				case OBJECT_TABLE:
! 					if (((CreateTableAsStmt *) parsetree)->is_select_into)
! 						tag = "SELECT INTO";
! 					else
! 						tag = "CREATE TABLE AS";
! 					break;
! 				case OBJECT_MATVIEW:
! 					tag = "CREATE MATERIALIZED VIEW";
! 					break;
! 				default:
! 					tag = "???";
! 			}
! 			break;
! 
! 		case T_LoadMatViewStmt:
! 			tag = "LOAD MATERIALIZED VIEW";
  			break;
  
  		case T_VariableSetStmt:
***************
*** 2629,2634 **** GetCommandLogLevel(Node *parsetree)
--- 2682,2691 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_LoadMatViewStmt:
+ 			lev = LOGSTMT_DDL;
+ 			break;
+ 
  		case T_VariableSetStmt:
  			lev = LOGSTMT_ALL;
  			break;
*** a/src/backend/utils/adt/dbsize.c
--- b/src/backend/utils/adt/dbsize.c
***************
*** 715,720 **** pg_relation_filenode(PG_FUNCTION_ARGS)
--- 715,721 ----
  	switch (relform->relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_INDEX:
  		case RELKIND_SEQUENCE:
  		case RELKIND_TOASTVALUE:
***************
*** 763,768 **** pg_relation_filepath(PG_FUNCTION_ARGS)
--- 764,770 ----
  	switch (relform->relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_INDEX:
  		case RELKIND_SEQUENCE:
  		case RELKIND_TOASTVALUE:
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 2285,2291 **** schema_get_xml_visible_tables(Oid nspid)
  	StringInfoData query;
  
  	initStringInfo(&query);
! 	appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'v') AND pg_catalog.has_table_privilege (oid, 'SELECT') ORDER BY relname;", nspid);
  
  	return query_to_oid_list(query.data);
  }
--- 2285,2291 ----
  	StringInfoData query;
  
  	initStringInfo(&query);
! 	appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'm', 'v') AND pg_catalog.has_table_privilege (oid, 'SELECT') ORDER BY relname;", nspid);
  
  	return query_to_oid_list(query.data);
  }
***************
*** 2311,2317 **** static List *
  database_get_xml_visible_tables(void)
  {
  	/* At the moment there is no order required here. */
! 	return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'v') AND pg_catalog.has_table_privilege (pg_class.oid, 'SELECT') AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
  }
  
  
--- 2311,2317 ----
  database_get_xml_visible_tables(void)
  {
  	/* At the moment there is no order required here. */
! 	return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'm', 'v') AND pg_catalog.has_table_privilege (pg_class.oid, 'SELECT') AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
  }
  
  
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 376,381 **** RelationParseRelOptions(Relation relation, HeapTuple tuple)
--- 376,382 ----
  		case RELKIND_TOASTVALUE:
  		case RELKIND_INDEX:
  		case RELKIND_VIEW:
+ 		case RELKIND_MATVIEW:
  			break;
  		default:
  			return;
***************
*** 1418,1423 **** formrdesc(const char *relationName, Oid relationReltype,
--- 1419,1425 ----
  	relation->rd_rel->reltuples = 0;
  	relation->rd_rel->relallvisible = 0;
  	relation->rd_rel->relkind = RELKIND_RELATION;
+ 	relation->rd_rel->relisvalid = true;
  	relation->rd_rel->relhasoids = hasoids;
  	relation->rd_rel->relnatts = (int16) natts;
  
***************
*** 2521,2526 **** RelationBuildLocalRelation(const char *relname,
--- 2523,2529 ----
  	rel->rd_rel->relhasoids = rel->rd_att->tdhasoid;
  	rel->rd_rel->relnatts = natts;
  	rel->rd_rel->reltype = InvalidOid;
+ 	rel->rd_rel->relisvalid = true;
  	/* needed when bootstrapping: */
  	rel->rd_rel->relowner = BOOTSTRAP_SUPERUSERID;
  
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
***************
*** 2030,2036 **** setup_privileges(void)
  	static char *privileges_setup[] = {
  		"UPDATE pg_class "
  		"  SET relacl = E'{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
! 		"  WHERE relkind IN ('r', 'v', 'S') AND relacl IS NULL;\n",
  		"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
  		"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
  		"REVOKE ALL ON pg_largeobject FROM PUBLIC;\n",
--- 2030,2036 ----
  	static char *privileges_setup[] = {
  		"UPDATE pg_class "
  		"  SET relacl = E'{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
! 		"  WHERE relkind IN ('r', 'v', 'm', 'S') AND relacl IS NULL;\n",
  		"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
  		"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
  		"REVOKE ALL ON pg_largeobject FROM PUBLIC;\n",
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 272,278 **** flagInhTables(TableInfo *tblinfo, int numTables,
  	{
  		/* Sequences and views never have parents */
  		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
! 			tblinfo[i].relkind == RELKIND_VIEW)
  			continue;
  
  		/* Don't bother computing anything for non-target tables, either */
--- 272,279 ----
  	{
  		/* Sequences and views never have parents */
  		if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
! 			tblinfo[i].relkind == RELKIND_VIEW ||
! 			tblinfo[i].relkind == RELKIND_MATVIEW)
  			continue;
  
  		/* Don't bother computing anything for non-target tables, either */
***************
*** 317,323 **** flagInhAttrs(TableInfo *tblinfo, int numTables)
  
  		/* Sequences and views never have parents */
  		if (tbinfo->relkind == RELKIND_SEQUENCE ||
! 			tbinfo->relkind == RELKIND_VIEW)
  			continue;
  
  		/* Don't bother computing anything for non-target tables, either */
--- 318,325 ----
  
  		/* Sequences and views never have parents */
  		if (tbinfo->relkind == RELKIND_SEQUENCE ||
! 			tbinfo->relkind == RELKIND_VIEW ||
! 			tbinfo->relkind == RELKIND_MATVIEW)
  			continue;
  
  		/* Don't bother computing anything for non-target tables, either */
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2909,2915 **** _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
  	const char *type = te->desc;
  
  	/* Use ALTER TABLE for views and sequences */
! 	if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0)
  		type = "TABLE";
  
  	/* objects named by a schema and name */
--- 2909,2916 ----
  	const char *type = te->desc;
  
  	/* Use ALTER TABLE for views and sequences */
! 	if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0||
! 		strcmp(type, "MATERIALIZED VIEW") == 0)
  		type = "TABLE";
  
  	/* objects named by a schema and name */
***************
*** 3141,3146 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 3142,3148 ----
  			strcmp(te->desc, "TABLE") == 0 ||
  			strcmp(te->desc, "TYPE") == 0 ||
  			strcmp(te->desc, "VIEW") == 0 ||
+ 			strcmp(te->desc, "MATERIALIZED VIEW") == 0 ||
  			strcmp(te->desc, "SEQUENCE") == 0 ||
  			strcmp(te->desc, "FOREIGN TABLE") == 0 ||
  			strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 194,199 **** static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo);
--- 194,200 ----
  static void dumpSequence(Archive *fout, TableInfo *tbinfo);
  static void dumpSequenceData(Archive *fout, TableDataInfo *tdinfo);
  static void dumpIndex(Archive *fout, IndxInfo *indxinfo);
+ static void dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo);
  static void dumpConstraint(Archive *fout, ConstraintInfo *coninfo);
  static void dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo);
  static void dumpTSParser(Archive *fout, TSParserInfo *prsinfo);
***************
*** 1042,1050 **** expand_table_name_patterns(Archive *fout,
  						  "SELECT c.oid"
  						  "\nFROM pg_catalog.pg_class c"
  		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! 						  "\nWHERE c.relkind in ('%c', '%c', '%c', '%c')\n",
  						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
! 						  RELKIND_FOREIGN_TABLE);
  		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
  							  false, "n.nspname", "c.relname", NULL,
  							  "pg_catalog.pg_table_is_visible(c.oid)");
--- 1043,1051 ----
  						  "SELECT c.oid"
  						  "\nFROM pg_catalog.pg_class c"
  		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! 						  "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
  						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
! 						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
  		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
  							  false, "n.nspname", "c.relname", NULL,
  							  "pg_catalog.pg_table_is_visible(c.oid)");
***************
*** 1605,1610 **** dumpTableData(Archive *fout, TableDataInfo *tdinfo)
--- 1606,1652 ----
  }
  
  /*
+  * loadMatViewData -
+  *	  load the contents of a single materialized view
+  *
+  * Actually, this just makes an ArchiveEntry for the LOAD MATERIALIZED VIEW
+  * statement.
+  */
+ static void
+ loadMatViewData(Archive *fout, TableDataInfo *tdinfo)
+ {
+ 	TableInfo  *tbinfo = tdinfo->tdtable;
+ 	PQExpBuffer q;
+ 
+ 	if (!(tbinfo->relisvalid))
+ 		return;
+ 
+ 	q = createPQExpBuffer();
+ 	appendPQExpBuffer(q, "LOAD MATERIALIZED VIEW %s;\n",
+ 					  fmtId(tbinfo->dobj.name));
+ 
+ 	ArchiveEntry(fout,
+ 				 tdinfo->dobj.catId,					/* catalog ID */
+ 				 tdinfo->dobj.dumpId,					/* dump ID */
+ 				 tbinfo->dobj.name,						/* Name */
+ 				 tbinfo->dobj.namespace->dobj.name,	/* Namespace */
+ 				 NULL,									/* Tablespace */
+ 				 tbinfo->rolname,						/* Owner */
+ 				 false,									/* with oids */
+ 				 "MATERIALIZED VIEW DATA",				/* Desc */
+ 				 SECTION_POST_DATA,						/* Section */
+ 				 q->data,								/* Create */
+ 				 "",									/* Del */
+ 				 NULL,									/* Copy */
+ 				 &(tbinfo->dobj.dumpId),				/* Deps */
+ 				 1,										/* # Deps */
+ 				 NULL,									/* Dumper */
+ 				 NULL);									/* Dumper Arg */
+ 
+ 	destroyPQExpBuffer(q);
+ }
+ 
+ /*
   * getTableData -
   *	  set up dumpable objects representing the contents of tables
   */
***************
*** 1658,1664 **** makeTableDataInfo(TableInfo *tbinfo, bool oids)
  	/* OK, let's dump it */
  	tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
  
! 	tdinfo->dobj.objType = DO_TABLE_DATA;
  
  	/*
  	 * Note: use tableoid 0 so that this object won't be mistaken for
--- 1700,1709 ----
  	/* OK, let's dump it */
  	tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
  
! 	if (tbinfo->relkind == RELKIND_MATVIEW)
! 		tdinfo->dobj.objType = DO_LOAD_MATVIEW;
! 	else
! 		tdinfo->dobj.objType = DO_TABLE_DATA;
  
  	/*
  	 * Note: use tableoid 0 so that this object won't be mistaken for
***************
*** 3902,3907 **** getTables(Archive *fout, int *numTables)
--- 3947,3953 ----
  	int			i_toastoid;
  	int			i_toastfrozenxid;
  	int			i_relpersistence;
+ 	int			i_relisvalid;
  	int			i_owning_tab;
  	int			i_owning_col;
  	int			i_reltablespace;
***************
*** 3946,3952 **** getTables(Archive *fout, int *numTables)
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "c.relpersistence, "
  						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 3992,3998 ----
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "c.relpersistence, c.relisvalid, "
  						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 3960,3972 **** getTables(Archive *fout, int *numTables)
  						  "d.objsubid = 0 AND "
  						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
  					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
! 						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
  						  "ORDER BY c.oid",
  						  username_subquery,
  						  RELKIND_SEQUENCE,
  						  RELKIND_RELATION, RELKIND_SEQUENCE,
  						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
! 						  RELKIND_FOREIGN_TABLE);
  	}
  	else if (fout->remoteVersion >= 90000)
  	{
--- 4006,4018 ----
  						  "d.objsubid = 0 AND "
  						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
  					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
! 						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
  						  "ORDER BY c.oid",
  						  username_subquery,
  						  RELKIND_SEQUENCE,
  						  RELKIND_RELATION, RELKIND_SEQUENCE,
  						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
! 						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
  	}
  	else if (fout->remoteVersion >= 90000)
  	{
***************
*** 3982,3988 **** getTables(Archive *fout, int *numTables)
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 4028,4034 ----
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 4017,4023 **** getTables(Archive *fout, int *numTables)
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 4063,4069 ----
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 4052,4058 **** getTables(Archive *fout, int *numTables)
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 4098,4104 ----
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 4088,4094 **** getTables(Archive *fout, int *numTables)
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 4134,4140 ----
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 4123,4129 **** getTables(Archive *fout, int *numTables)
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
--- 4169,4175 ----
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "d.refobjid AS owning_tab, "
  						  "d.refobjsubid AS owning_col, "
***************
*** 4154,4160 **** getTables(Archive *fout, int *numTables)
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
--- 4200,4206 ----
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
***************
*** 4180,4186 **** getTables(Archive *fout, int *numTables)
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
--- 4226,4232 ----
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
***************
*** 4216,4222 **** getTables(Archive *fout, int *numTables)
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
--- 4262,4268 ----
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
! 						  "'p' AS relpersistence, 't'::bool as relisvalid, "
  						  "NULL AS reloftype, "
  						  "NULL::oid AS owning_tab, "
  						  "NULL::int4 AS owning_col, "
***************
*** 4264,4269 **** getTables(Archive *fout, int *numTables)
--- 4310,4316 ----
  	i_toastoid = PQfnumber(res, "toid");
  	i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
  	i_relpersistence = PQfnumber(res, "relpersistence");
+ 	i_relisvalid = PQfnumber(res, "relisvalid");
  	i_owning_tab = PQfnumber(res, "owning_tab");
  	i_owning_col = PQfnumber(res, "owning_col");
  	i_reltablespace = PQfnumber(res, "reltablespace");
***************
*** 4305,4310 **** getTables(Archive *fout, int *numTables)
--- 4352,4358 ----
  		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
  		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
  		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
+ 		tblinfo[i].relisvalid = (strcmp(PQgetvalue(res, i, i_relisvalid), "t") == 0);
  		tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
  		tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
  		tblinfo[i].toast_frozenxid = atooid(PQgetvalue(res, i, i_toastfrozenxid));
***************
*** 4500,4507 **** getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
  	{
  		TableInfo  *tbinfo = &tblinfo[i];
  
! 		/* Only plain tables have indexes */
! 		if (tbinfo->relkind != RELKIND_RELATION || !tbinfo->hasindex)
  			continue;
  
  		/* Ignore indexes of tables not to be dumped */
--- 4548,4558 ----
  	{
  		TableInfo  *tbinfo = &tblinfo[i];
  
! 		/* Only plain tables and materialized views have indexes. */
! 		if (tbinfo->relkind != RELKIND_RELATION &&
! 			tbinfo->relkind != RELKIND_MATVIEW)
! 			continue;
! 		if (!tbinfo->hasindex)
  			continue;
  
  		/* Ignore indexes of tables not to be dumped */
***************
*** 4715,4721 **** getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
  		{
  			char		contype;
  
! 			indxinfo[j].dobj.objType = DO_INDEX;
  			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
  			indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
  			AssignDumpId(&indxinfo[j].dobj);
--- 4766,4775 ----
  		{
  			char		contype;
  
! 			if (tbinfo->relkind == RELKIND_MATVIEW)
! 				indxinfo[j].dobj.objType = DO_MATVIEW_INDEX;
! 			else
! 				indxinfo[j].dobj.objType = DO_INDEX;
  			indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
  			indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
  			AssignDumpId(&indxinfo[j].dobj);
***************
*** 5083,5094 **** getRules(Archive *fout, int *numRules)
  		if (ruleinfo[i].ruletable)
  		{
  			/*
! 			 * If the table is a view, force its ON SELECT rule to be sorted
! 			 * before the view itself --- this ensures that any dependencies
! 			 * for the rule affect the table's positioning. Other rules are
! 			 * forced to appear after their table.
  			 */
! 			if (ruleinfo[i].ruletable->relkind == RELKIND_VIEW &&
  				ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead)
  			{
  				addObjectDependency(&ruleinfo[i].ruletable->dobj,
--- 5137,5150 ----
  		if (ruleinfo[i].ruletable)
  		{
  			/*
! 			 * If the table is a view or materialized view, force its ON
! 			 * SELECT rule to be sorted before the view itself --- this
! 			 * ensures that any dependencies for the rule affect the table's
! 			 * positioning. Other rules are forced to appear after their
! 			 * table.
  			 */
! 			if ((ruleinfo[i].ruletable->relkind == RELKIND_VIEW ||
! 			     ruleinfo[i].ruletable->relkind == RELKIND_MATVIEW) &&
  				ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead)
  			{
  				addObjectDependency(&ruleinfo[i].ruletable->dobj,
***************
*** 7294,7299 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7350,7361 ----
  		case DO_INDEX:
  			dumpIndex(fout, (IndxInfo *) dobj);
  			break;
+ 		case DO_LOAD_MATVIEW:
+ 			loadMatViewData(fout, (TableDataInfo *) dobj);
+ 			break;
+ 		case DO_MATVIEW_INDEX:
+ 			dumpMatViewIndex(fout, (IndxInfo *) dobj);
+ 			break;
  		case DO_RULE:
  			dumpRule(fout, (RuleInfo *) dobj);
  			break;
***************
*** 12290,12306 **** dumpTable(Archive *fout, TableInfo *tbinfo)
  }
  
  /*
   * dumpTableSchema
   *	  write the declaration (not data) of one user-defined table or view
   */
  static void
  dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  {
- 	PQExpBuffer query = createPQExpBuffer();
  	PQExpBuffer q = createPQExpBuffer();
  	PQExpBuffer delq = createPQExpBuffer();
  	PQExpBuffer labelq = createPQExpBuffer();
- 	PGresult   *res;
  	int			numParents;
  	TableInfo **parents;
  	int			actual_atts;	/* number of attrs in this CREATE statement */
--- 12352,12423 ----
  }
  
  /*
+  * Create the AS clause for a view or materialized view. The semicolon is
+  * stripped because a materialized view must add a WITH NO DATA clause.
+  * 
+  * This returns a new buffer which must be freed by the caller.
+  */
+ static PQExpBuffer
+ createViewAsClause(Archive *fout, TableInfo *tbinfo)
+ {
+ 	PQExpBuffer query = createPQExpBuffer();
+ 	PQExpBuffer result = createPQExpBuffer();
+ 	PGresult   *res;
+ 	int			len;
+ 
+ 	/* Fetch the view definition */
+ 	if (fout->remoteVersion >= 70300)
+ 	{
+ 		/* Beginning in 7.3, viewname is not unique; rely on OID */
+ 		appendPQExpBuffer(query,
+ 						  "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
+ 						  tbinfo->dobj.catId.oid);
+ 	}
+ 	else
+ 	{
+ 		appendPQExpBuffer(query, "SELECT definition AS viewdef "
+ 						  "FROM pg_views WHERE viewname = ");
+ 		appendStringLiteralAH(query, tbinfo->dobj.name, fout);
+ 		appendPQExpBuffer(query, ";");
+ 	}
+ 
+ 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ 
+ 	if (PQntuples(res) != 1)
+ 	{
+ 		if (PQntuples(res) < 1)
+ 			exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n",
+ 						  tbinfo->dobj.name);
+ 		else
+ 			exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
+ 						  tbinfo->dobj.name);
+ 	}
+ 
+ 	len = PQgetlength(res, 0, 0);
+ 
+ 	if (len == 0)
+ 		exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
+ 					  tbinfo->dobj.name);
+ 
+ 	/* Strip off the trailing semicolon so that other things may follow. */
+ 	appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);
+ 
+ 	PQclear(res);
+ 	destroyPQExpBuffer(query);
+ 
+ 	return result;
+ }
+ 
+ /*
   * dumpTableSchema
   *	  write the declaration (not data) of one user-defined table or view
   */
  static void
  dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  {
  	PQExpBuffer q = createPQExpBuffer();
  	PQExpBuffer delq = createPQExpBuffer();
  	PQExpBuffer labelq = createPQExpBuffer();
  	int			numParents;
  	TableInfo **parents;
  	int			actual_atts;	/* number of attrs in this CREATE statement */
***************
*** 12321,12364 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  	/* Is it a table or a view? */
  	if (tbinfo->relkind == RELKIND_VIEW)
  	{
! 		char	   *viewdef;
  
  		reltypename = "VIEW";
  
- 		/* Fetch the view definition */
- 		if (fout->remoteVersion >= 70300)
- 		{
- 			/* Beginning in 7.3, viewname is not unique; rely on OID */
- 			appendPQExpBuffer(query,
- 							  "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
- 							  tbinfo->dobj.catId.oid);
- 		}
- 		else
- 		{
- 			appendPQExpBuffer(query, "SELECT definition AS viewdef "
- 							  "FROM pg_views WHERE viewname = ");
- 			appendStringLiteralAH(query, tbinfo->dobj.name, fout);
- 			appendPQExpBuffer(query, ";");
- 		}
- 
- 		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
- 
- 		if (PQntuples(res) != 1)
- 		{
- 			if (PQntuples(res) < 1)
- 				exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n",
- 							  tbinfo->dobj.name);
- 			else
- 				exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
- 							  tbinfo->dobj.name);
- 		}
- 
- 		viewdef = PQgetvalue(res, 0, 0);
- 
- 		if (strlen(viewdef) == 0)
- 			exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
- 						  tbinfo->dobj.name);
- 
  		/*
  		 * DROP must be fully qualified in case same name appears in
  		 * pg_catalog
--- 12438,12447 ----
  	/* Is it a table or a view? */
  	if (tbinfo->relkind == RELKIND_VIEW)
  	{
! 		PQExpBuffer result;
  
  		reltypename = "VIEW";
  
  		/*
  		 * DROP must be fully qualified in case same name appears in
  		 * pg_catalog
***************
*** 12375,12423 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  		appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
  		if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
  			appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
! 		appendPQExpBuffer(q, " AS\n    %s\n", viewdef);
  
  		appendPQExpBuffer(labelq, "VIEW %s",
  						  fmtId(tbinfo->dobj.name));
- 
- 		PQclear(res);
  	}
  	else
  	{
! 		if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
  		{
! 			int			i_srvname;
! 			int			i_ftoptions;
! 
! 			reltypename = "FOREIGN TABLE";
! 
! 			/* retrieve name of foreign server and generic options */
! 			appendPQExpBuffer(query,
! 							  "SELECT fs.srvname, "
! 							  "pg_catalog.array_to_string(ARRAY("
! 							  "SELECT pg_catalog.quote_ident(option_name) || "
! 							  "' ' || pg_catalog.quote_literal(option_value) "
! 							"FROM pg_catalog.pg_options_to_table(ftoptions) "
! 							  "ORDER BY option_name"
! 							  "), E',\n    ') AS ftoptions "
! 							  "FROM pg_catalog.pg_foreign_table ft "
! 							  "JOIN pg_catalog.pg_foreign_server fs "
! 							  "ON (fs.oid = ft.ftserver) "
! 							  "WHERE ft.ftrelid = '%u'",
! 							  tbinfo->dobj.catId.oid);
! 			res = ExecuteSqlQueryForSingleRow(fout, query->data);
! 			i_srvname = PQfnumber(res, "srvname");
! 			i_ftoptions = PQfnumber(res, "ftoptions");
! 			srvname = pg_strdup(PQgetvalue(res, 0, i_srvname));
! 			ftoptions = pg_strdup(PQgetvalue(res, 0, i_ftoptions));
! 			PQclear(res);
! 		}
! 		else
! 		{
! 			reltypename = "TABLE";
! 			srvname = NULL;
! 			ftoptions = NULL;
  		}
  		numParents = tbinfo->numParents;
  		parents = tbinfo->parents;
  
--- 12458,12517 ----
  		appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
  		if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
  			appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
! 		result = createViewAsClause(fout, tbinfo);
! 		appendPQExpBuffer(q, " AS\n    %s;\n", result->data);
! 		destroyPQExpBuffer(result);
  
  		appendPQExpBuffer(labelq, "VIEW %s",
  						  fmtId(tbinfo->dobj.name));
  	}
  	else
  	{
! 		switch (tbinfo->relkind)
  		{
! 			case (RELKIND_FOREIGN_TABLE):
! 			{
! 				PQExpBuffer query = createPQExpBuffer();
! 				PGresult   *res;
! 				int			i_srvname;
! 				int			i_ftoptions;
! 
! 				reltypename = "FOREIGN TABLE";
! 
! 				/* retrieve name of foreign server and generic options */
! 				appendPQExpBuffer(query,
! 								  "SELECT fs.srvname, "
! 								  "pg_catalog.array_to_string(ARRAY("
! 								  "SELECT pg_catalog.quote_ident(option_name) || "
! 								  "' ' || pg_catalog.quote_literal(option_value) "
! 								"FROM pg_catalog.pg_options_to_table(ftoptions) "
! 								  "ORDER BY option_name"
! 								  "), E',\n    ') AS ftoptions "
! 								  "FROM pg_catalog.pg_foreign_table ft "
! 								  "JOIN pg_catalog.pg_foreign_server fs "
! 								  "ON (fs.oid = ft.ftserver) "
! 								  "WHERE ft.ftrelid = '%u'",
! 								  tbinfo->dobj.catId.oid);
! 				res = ExecuteSqlQueryForSingleRow(fout, query->data);
! 				i_srvname = PQfnumber(res, "srvname");
! 				i_ftoptions = PQfnumber(res, "ftoptions");
! 				srvname = pg_strdup(PQgetvalue(res, 0, i_srvname));
! 				ftoptions = pg_strdup(PQgetvalue(res, 0, i_ftoptions));
! 				PQclear(res);
! 				destroyPQExpBuffer(query);
! 				break;
! 			}
! 			case (RELKIND_MATVIEW):
! 				reltypename = "MATERIALIZED VIEW";
! 				srvname = NULL;
! 				ftoptions = NULL;
! 				break;
! 			default:
! 				reltypename = "TABLE";
! 				srvname = NULL;
! 				ftoptions = NULL;
  		}
+ 
  		numParents = tbinfo->numParents;
  		parents = tbinfo->parents;
  
***************
*** 12489,12497 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  				actual_atts++;
  
  				/* Attribute name */
! 				appendPQExpBuffer(q, "%s ",
  								  fmtId(tbinfo->attnames[j]));
  
  				if (tbinfo->attisdropped[j])
  				{
  					/*
--- 12583,12595 ----
  				actual_atts++;
  
  				/* Attribute name */
! 				appendPQExpBuffer(q, "%s",
  								  fmtId(tbinfo->attnames[j]));
  
+ 				/* Materialized views just have column names, not types. */
+ 				if (tbinfo->relkind == RELKIND_MATVIEW)
+ 					continue;
+ 
  				if (tbinfo->attisdropped[j])
  				{
  					/*
***************
*** 12499,12505 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  					 * so we will not have gotten a valid type name; insert
  					 * INTEGER as a stopgap.  We'll clean things up later.
  					 */
! 					appendPQExpBuffer(q, "INTEGER /* dummy */");
  					/* Skip all the rest, too */
  					continue;
  				}
--- 12597,12603 ----
  					 * so we will not have gotten a valid type name; insert
  					 * INTEGER as a stopgap.  We'll clean things up later.
  					 */
! 					appendPQExpBuffer(q, " INTEGER /* dummy */");
  					/* Skip all the rest, too */
  					continue;
  				}
***************
*** 12507,12523 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  				/* Attribute type */
  				if (tbinfo->reloftype && !binary_upgrade)
  				{
! 					appendPQExpBuffer(q, "WITH OPTIONS");
  				}
  				else if (fout->remoteVersion >= 70100)
  				{
! 					appendPQExpBuffer(q, "%s",
  									  tbinfo->atttypnames[j]);
  				}
  				else
  				{
  					/* If no format_type, fake it */
! 					appendPQExpBuffer(q, "%s",
  									  myFormatType(tbinfo->atttypnames[j],
  												   tbinfo->atttypmod[j]));
  				}
--- 12605,12621 ----
  				/* Attribute type */
  				if (tbinfo->reloftype && !binary_upgrade)
  				{
! 					appendPQExpBuffer(q, " WITH OPTIONS");
  				}
  				else if (fout->remoteVersion >= 70100)
  				{
! 					appendPQExpBuffer(q, " %s",
  									  tbinfo->atttypnames[j]);
  				}
  				else
  				{
  					/* If no format_type, fake it */
! 					appendPQExpBuffer(q, " %s",
  									  myFormatType(tbinfo->atttypnames[j],
  												   tbinfo->atttypmod[j]));
  				}
***************
*** 12624,12630 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  		if (ftoptions && ftoptions[0])
  			appendPQExpBuffer(q, "\nOPTIONS (\n    %s\n)", ftoptions);
  
! 		appendPQExpBuffer(q, ";\n");
  
  		/*
  		 * To create binary-compatible heap files, we have to ensure the same
--- 12722,12741 ----
  		if (ftoptions && ftoptions[0])
  			appendPQExpBuffer(q, "\nOPTIONS (\n    %s\n)", ftoptions);
  
! 		/*
! 		 * For materialized views, create the AS clause just like a view.
! 		 */
! 		if (tbinfo->relkind == RELKIND_MATVIEW)
! 		{
! 			PQExpBuffer result;
! 
! 			result = createViewAsClause(fout, tbinfo);
! 			appendPQExpBuffer(q, " AS\n    %s\n  WITH NO DATA;\n",
! 							  result->data);
! 			destroyPQExpBuffer(result);
! 		}
! 		else
! 			appendPQExpBuffer(q, ";\n");
  
  		/*
  		 * To create binary-compatible heap files, we have to ensure the same
***************
*** 12880,12886 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  		dumpTableConstraintComment(fout, constr);
  	}
  
- 	destroyPQExpBuffer(query);
  	destroyPQExpBuffer(q);
  	destroyPQExpBuffer(delq);
  	destroyPQExpBuffer(labelq);
--- 12991,12996 ----
***************
*** 13049,13054 **** dumpIndex(Archive *fout, IndxInfo *indxinfo)
--- 13159,13240 ----
  }
  
  /*
+  * dumpMatViewIndex
+  *	  write out to fout a user-defined index
+  */
+ static void
+ dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo)
+ {
+ 	TableInfo  *tbinfo = indxinfo->indextable;
+ 	PQExpBuffer q;
+ 	PQExpBuffer delq;
+ 	PQExpBuffer labelq;
+ 
+ 	if (dataOnly)
+ 		return;
+ 
+ 	q = createPQExpBuffer();
+ 	delq = createPQExpBuffer();
+ 	labelq = createPQExpBuffer();
+ 
+ 	appendPQExpBuffer(labelq, "INDEX %s",
+ 					  fmtId(indxinfo->dobj.name));
+ 
+ 	/*
+ 	 * If there's an associated constraint, don't dump the index per se, but
+ 	 * do dump any comment for it.	(This is safe because dependency ordering
+ 	 * will have ensured the constraint is emitted first.)
+ 	 */
+ 	if (indxinfo->indexconstraint == 0)
+ 	{
+ 		if (binary_upgrade)
+ 			binary_upgrade_set_pg_class_oids(fout, q,
+ 											 indxinfo->dobj.catId.oid, true);
+ 
+ 		/* Plain secondary index */
+ 		appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef);
+ 
+ 		/* If the index is clustered, we need to record that. */
+ 		if (indxinfo->indisclustered)
+ 		{
+ 			appendPQExpBuffer(q, "\nALTER TABLE %s CLUSTER",
+ 							  fmtId(tbinfo->dobj.name));
+ 			appendPQExpBuffer(q, " ON %s;\n",
+ 							  fmtId(indxinfo->dobj.name));
+ 		}
+ 
+ 		/*
+ 		 * DROP must be fully qualified in case same name appears in
+ 		 * pg_catalog
+ 		 */
+ 		appendPQExpBuffer(delq, "DROP INDEX %s.",
+ 						  fmtId(tbinfo->dobj.namespace->dobj.name));
+ 		appendPQExpBuffer(delq, "%s;\n",
+ 						  fmtId(indxinfo->dobj.name));
+ 
+ 		ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId,
+ 					 indxinfo->dobj.name,
+ 					 tbinfo->dobj.namespace->dobj.name,
+ 					 indxinfo->tablespace,
+ 					 tbinfo->rolname, false,
+ 					 "INDEX", SECTION_POST_DATA,
+ 					 q->data, delq->data, NULL,
+ 					 NULL, 0,
+ 					 NULL, NULL);
+ 	}
+ 
+ 	/* Dump Index Comments */
+ 	dumpComment(fout, labelq->data,
+ 				tbinfo->dobj.namespace->dobj.name,
+ 				tbinfo->rolname,
+ 				indxinfo->dobj.catId, 0, indxinfo->dobj.dumpId);
+ 
+ 	destroyPQExpBuffer(q);
+ 	destroyPQExpBuffer(delq);
+ 	destroyPQExpBuffer(labelq);
+ }
+ 
+ /*
   * dumpConstraint
   *	  write out to fout a user-defined constraint
   */
***************
*** 14374,14379 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
--- 14560,14567 ----
  				addObjectDependency(postDataBound, dobj->dumpId);
  				break;
  			case DO_INDEX:
+ 			case DO_LOAD_MATVIEW:
+ 			case DO_MATVIEW_INDEX:
  			case DO_TRIGGER:
  			case DO_EVENT_TRIGGER:
  			case DO_DEFAULT_ACL:
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 121,127 **** typedef enum
  	DO_BLOB_DATA,
  	DO_PRE_DATA_BOUNDARY,
  	DO_POST_DATA_BOUNDARY,
! 	DO_EVENT_TRIGGER
  } DumpableObjectType;
  
  typedef struct _dumpableObject
--- 121,129 ----
  	DO_BLOB_DATA,
  	DO_PRE_DATA_BOUNDARY,
  	DO_POST_DATA_BOUNDARY,
! 	DO_EVENT_TRIGGER,
! 	DO_LOAD_MATVIEW,
! 	DO_MATVIEW_INDEX
  } DumpableObjectType;
  
  typedef struct _dumpableObject
***************
*** 252,257 **** typedef struct _tableInfo
--- 254,260 ----
  	bool		hasrules;		/* does it have any rules? */
  	bool		hastriggers;	/* does it have any triggers? */
  	bool		hasoids;		/* does it have OIDs? */
+ 	bool		relisvalid;		/* is valid for use in queries */
  	uint32		frozenxid;		/* for restore frozen xid */
  	Oid			toast_oid;		/* for restore toast frozen xid */
  	uint32		toast_frozenxid;	/* for restore toast frozen xid */
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 69,75 **** static const int oldObjectTypePriority[] =
  	12,							/* DO_BLOB_DATA */
  	10,							/* DO_PRE_DATA_BOUNDARY */
  	13,							/* DO_POST_DATA_BOUNDARY */
! 	20							/* DO_EVENT_TRIGGER */
  };
  
  /*
--- 69,77 ----
  	12,							/* DO_BLOB_DATA */
  	10,							/* DO_PRE_DATA_BOUNDARY */
  	13,							/* DO_POST_DATA_BOUNDARY */
! 	20,							/* DO_EVENT_TRIGGER */
! 	15,							/* DO_LOAD_MATVIEW */
! 	15							/* DO_MATVIEW_INDEX */
  };
  
  /*
***************
*** 97,106 **** static const int newObjectTypePriority[] =
  	18,							/* DO_TABLE */
  	20,							/* DO_ATTRDEF */
  	27,							/* DO_INDEX */
! 	28,							/* DO_RULE */
! 	29,							/* DO_TRIGGER */
  	26,							/* DO_CONSTRAINT */
! 	30,							/* DO_FK_CONSTRAINT */
  	2,							/* DO_PROCLANG */
  	10,							/* DO_CAST */
  	23,							/* DO_TABLE_DATA */
--- 99,108 ----
  	18,							/* DO_TABLE */
  	20,							/* DO_ATTRDEF */
  	27,							/* DO_INDEX */
! 	29,							/* DO_RULE */
! 	30,							/* DO_TRIGGER */
  	26,							/* DO_CONSTRAINT */
! 	31,							/* DO_FK_CONSTRAINT */
  	2,							/* DO_PROCLANG */
  	10,							/* DO_CAST */
  	23,							/* DO_TABLE_DATA */
***************
*** 111,122 **** static const int newObjectTypePriority[] =
  	15,							/* DO_TSCONFIG */
  	16,							/* DO_FDW */
  	17,							/* DO_FOREIGN_SERVER */
! 	31,							/* DO_DEFAULT_ACL */
  	21,							/* DO_BLOB */
  	24,							/* DO_BLOB_DATA */
  	22,							/* DO_PRE_DATA_BOUNDARY */
  	25,							/* DO_POST_DATA_BOUNDARY */
! 	32							/* DO_EVENT_TRIGGER */
  };
  
  static DumpId preDataBoundId;
--- 113,126 ----
  	15,							/* DO_TSCONFIG */
  	16,							/* DO_FDW */
  	17,							/* DO_FOREIGN_SERVER */
! 	32,							/* DO_DEFAULT_ACL */
  	21,							/* DO_BLOB */
  	24,							/* DO_BLOB_DATA */
  	22,							/* DO_PRE_DATA_BOUNDARY */
  	25,							/* DO_POST_DATA_BOUNDARY */
! 	33,							/* DO_EVENT_TRIGGER */
! 	28,							/* DO_LOAD_MATVIEW */
! 	28							/* DO_MATVIEW_INDEX */
  };
  
  static DumpId preDataBoundId;
***************
*** 1153,1158 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
--- 1157,1172 ----
  					 "INDEX %s  (ID %d OID %u)",
  					 obj->name, obj->dumpId, obj->catId.oid);
  			return;
+ 		case DO_LOAD_MATVIEW:
+ 			snprintf(buf, bufsize,
+ 					 "LOAD MATERIALIZED VIEW %s  (ID %d OID %u)",
+ 					 obj->name, obj->dumpId, obj->catId.oid);
+ 			return;
+ 		case DO_MATVIEW_INDEX:
+ 			snprintf(buf, bufsize,
+ 					 "MATERIALIZED VIEW INDEX %s  (ID %d OID %u)",
+ 					 obj->name, obj->dumpId, obj->catId.oid);
+ 			return;
  		case DO_RULE:
  			snprintf(buf, bufsize,
  					 "RULE %s  (ID %d OID %u)",
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
***************
*** 355,361 **** exec_command(const char *cmd,
  					success = describeTableDetails(pattern, show_verbose, show_system);
  				else
  					/* standard listing of interesting things */
! 					success = listTables("tvsE", NULL, show_verbose, show_system);
  				break;
  			case 'a':
  				success = describeAggregates(pattern, show_verbose, show_system);
--- 355,361 ----
  					success = describeTableDetails(pattern, show_verbose, show_system);
  				else
  					/* standard listing of interesting things */
! 					success = listTables("tvmsE", NULL, show_verbose, show_system);
  				break;
  			case 'a':
  				success = describeAggregates(pattern, show_verbose, show_system);
***************
*** 422,427 **** exec_command(const char *cmd,
--- 422,428 ----
  				break;
  			case 't':
  			case 'v':
+ 			case 'm':
  			case 'i':
  			case 's':
  			case 'E':
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 715,725 **** permissionsList(const char *pattern)
  	printfPQExpBuffer(&buf,
  					  "SELECT n.nspname as \"%s\",\n"
  					  "  c.relname as \"%s\",\n"
! 					  "  CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
  					  "  ",
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"),
! 	   gettext_noop("table"), gettext_noop("view"), gettext_noop("sequence"),
  					  gettext_noop("foreign table"),
  					  gettext_noop("Type"));
  
--- 715,734 ----
  	printfPQExpBuffer(&buf,
  					  "SELECT n.nspname as \"%s\",\n"
  					  "  c.relname as \"%s\",\n"
! 					  "  CASE c.relkind"
! 					  " WHEN 'r' THEN '%s'"
! 					  " WHEN 'v' THEN '%s'"
! 					  " WHEN 'm' THEN '%s'"
! 					  " WHEN 'S' THEN '%s'"
! 					  " WHEN 'f' THEN '%s'"
! 					  " END as \"%s\",\n"
  					  "  ",
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"),
! 					  gettext_noop("table"),
! 					  gettext_noop("view"),
! 					  gettext_noop("materialized view"),
! 					  gettext_noop("sequence"),
  					  gettext_noop("foreign table"),
  					  gettext_noop("Type"));
  
***************
*** 736,742 **** permissionsList(const char *pattern)
  
  	appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
  	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! 					  "WHERE c.relkind IN ('r', 'v', 'S', 'f')\n");
  
  	/*
  	 * Unless a schema pattern is specified, we suppress system and temp
--- 745,751 ----
  
  	appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
  	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! 					  "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
  
  	/*
  	 * Unless a schema pattern is specified, we suppress system and temp
***************
*** 1308,1313 **** describeOneTableDetails(const char *schemaname,
--- 1317,1323 ----
  		 * types, and foreign tables (c.f. CommentObject() in comment.c).
  		 */
  		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ 			tableinfo.relkind == 'm' ||
  			tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
  			appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
  	}
***************
*** 1336,1341 **** describeOneTableDetails(const char *schemaname,
--- 1346,1359 ----
  			printfPQExpBuffer(&title, _("View \"%s.%s\""),
  							  schemaname, relationname);
  			break;
+ 		case 'm':
+ 			if (tableinfo.relpersistence == 'u')
+ 				printfPQExpBuffer(&title, _("Unlogged materialized view \"%s.%s\""),
+ 								  schemaname, relationname);
+ 			else
+ 				printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""),
+ 								  schemaname, relationname);
+ 			break;
  		case 'S':
  			printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
  							  schemaname, relationname);
***************
*** 1378,1383 **** describeOneTableDetails(const char *schemaname,
--- 1396,1402 ----
  	cols = 2;
  
  	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ 		tableinfo.relkind == 'm' ||
  		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
  	{
  		show_modifiers = true;
***************
*** 1397,1406 **** describeOneTableDetails(const char *schemaname,
  	if (verbose)
  	{
  		headers[cols++] = gettext_noop("Storage");
! 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  			headers[cols++] = gettext_noop("Stats target");
  		/* Column comments, if the relkind supports this feature. */
  		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
  			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
  			headers[cols++] = gettext_noop("Description");
  	}
--- 1416,1427 ----
  	if (verbose)
  	{
  		headers[cols++] = gettext_noop("Storage");
! 		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 			tableinfo.relkind == 'f')
  			headers[cols++] = gettext_noop("Stats target");
  		/* Column comments, if the relkind supports this feature. */
  		if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ 			tableinfo.relkind == 'm' ||
  			tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
  			headers[cols++] = gettext_noop("Description");
  	}
***************
*** 1411,1418 **** describeOneTableDetails(const char *schemaname,
  	for (i = 0; i < cols; i++)
  		printTableAddHeader(&cont, headers[i], true, 'l');
  
! 	/* Check if table is a view */
! 	if (tableinfo.relkind == 'v' && verbose)
  	{
  		PGresult   *result;
  
--- 1432,1439 ----
  	for (i = 0; i < cols; i++)
  		printTableAddHeader(&cont, headers[i], true, 'l');
  
! 	/* Check if table is a view or materialized view */
! 	if ((tableinfo.relkind == 'v' || tableinfo.relkind == 'm') && verbose)
  	{
  		PGresult   *result;
  
***************
*** 1500,1506 **** describeOneTableDetails(const char *schemaname,
  							  false, false);
  
  			/* Statistics target, if the relkind supports this feature */
! 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  			{
  				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
  								  false, false);
--- 1521,1528 ----
  							  false, false);
  
  			/* Statistics target, if the relkind supports this feature */
! 			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 				tableinfo.relkind == 'f')
  			{
  				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
  								  false, false);
***************
*** 1508,1513 **** describeOneTableDetails(const char *schemaname,
--- 1530,1536 ----
  
  			/* Column comments, if the relkind supports this feature. */
  			if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ 				tableinfo.relkind == 'm' ||
  				tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
  				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
  								  false, false);
***************
*** 1604,1647 **** describeOneTableDetails(const char *schemaname,
  
  		PQclear(result);
  	}
- 	else if (view_def)
- 	{
- 		PGresult   *result = NULL;
- 
- 		/* Footer information about a view */
- 		printTableAddFooter(&cont, _("View definition:"));
- 		printTableAddFooter(&cont, view_def);
- 
- 		/* print rules */
- 		if (tableinfo.hasrules)
- 		{
- 			printfPQExpBuffer(&buf,
- 							  "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
- 							  "FROM pg_catalog.pg_rewrite r\n"
- 			"WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
- 							  oid);
- 			result = PSQLexec(buf.data, false);
- 			if (!result)
- 				goto error_return;
- 
- 			if (PQntuples(result) > 0)
- 			{
- 				printTableAddFooter(&cont, _("Rules:"));
- 				for (i = 0; i < PQntuples(result); i++)
- 				{
- 					const char *ruledef;
- 
- 					/* Everything after "CREATE RULE" is echoed verbatim */
- 					ruledef = PQgetvalue(result, i, 1);
- 					ruledef += 12;
- 
- 					printfPQExpBuffer(&buf, " %s", ruledef);
- 					printTableAddFooter(&cont, buf.data);
- 				}
- 			}
- 			PQclear(result);
- 		}
- 	}
  	else if (tableinfo.relkind == 'S')
  	{
  		/* Footer information about a sequence */
--- 1627,1632 ----
***************
*** 1680,1686 **** describeOneTableDetails(const char *schemaname,
  		 */
  		PQclear(result);
  	}
! 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  	{
  		/* Footer information about a table */
  		PGresult   *result = NULL;
--- 1665,1672 ----
  		 */
  		PQclear(result);
  	}
! 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 			 tableinfo.relkind == 'f')
  	{
  		/* Footer information about a table */
  		PGresult   *result = NULL;
***************
*** 1881,1887 **** describeOneTableDetails(const char *schemaname,
  		}
  
  		/* print rules */
! 		if (tableinfo.hasrules)
  		{
  			if (pset.sversion >= 80300)
  			{
--- 1867,1873 ----
  		}
  
  		/* print rules */
! 		if (tableinfo.hasrules && tableinfo.relkind != 'm')
  		{
  			if (pset.sversion >= 80300)
  			{
***************
*** 1976,1981 **** describeOneTableDetails(const char *schemaname,
--- 1962,2006 ----
  		}
  	}
  
+ 	if (view_def)
+ 	{
+ 		PGresult   *result = NULL;
+ 
+ 		/* Footer information about a view */
+ 		printTableAddFooter(&cont, _("View definition:"));
+ 		printTableAddFooter(&cont, view_def);
+ 
+ 		/* print rules */
+ 		if (tableinfo.hasrules)
+ 		{
+ 			printfPQExpBuffer(&buf,
+ 							  "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
+ 							  "FROM pg_catalog.pg_rewrite r\n"
+ 			"WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
+ 							  oid);
+ 			result = PSQLexec(buf.data, false);
+ 			if (!result)
+ 				goto error_return;
+ 
+ 			if (PQntuples(result) > 0)
+ 			{
+ 				printTableAddFooter(&cont, _("Rules:"));
+ 				for (i = 0; i < PQntuples(result); i++)
+ 				{
+ 					const char *ruledef;
+ 
+ 					/* Everything after "CREATE RULE" is echoed verbatim */
+ 					ruledef = PQgetvalue(result, i, 1);
+ 					ruledef += 12;
+ 
+ 					printfPQExpBuffer(&buf, " %s", ruledef);
+ 					printTableAddFooter(&cont, buf.data);
+ 				}
+ 			}
+ 			PQclear(result);
+ 		}
+ 	}
+ 
  	/*
  	 * Print triggers next, if any (but only user-defined triggers).  This
  	 * could apply to either a table or a view.
***************
*** 2099,2105 **** describeOneTableDetails(const char *schemaname,
  	/*
  	 * Finish printing the footer information about a table.
  	 */
! 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  	{
  		PGresult   *result;
  		int			tuples;
--- 2124,2131 ----
  	/*
  	 * Finish printing the footer information about a table.
  	 */
! 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 		tableinfo.relkind == 'f')
  	{
  		PGresult   *result;
  		int			tuples;
***************
*** 2296,2302 **** add_tablespace_footer(printTableContent *const cont, char relkind,
  					  Oid tablespace, const bool newline)
  {
  	/* relkinds for which we support tablespaces */
! 	if (relkind == 'r' || relkind == 'i')
  	{
  		/*
  		 * We ignore the database default tablespace so that users not using
--- 2322,2328 ----
  					  Oid tablespace, const bool newline)
  {
  	/* relkinds for which we support tablespaces */
! 	if (relkind == 'r' || relkind == 'm' || relkind == 'i')
  	{
  		/*
  		 * We ignore the database default tablespace so that users not using
***************
*** 2578,2583 **** listDbRoleSettings(const char *pattern, const char *pattern2)
--- 2604,2610 ----
   * t - tables
   * i - indexes
   * v - views
+  * m - materialized views
   * s - sequences
   * E - foreign table (Note: different from 'f', the relkind value)
   * (any order of the above is fine)
***************
*** 2589,2594 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2616,2622 ----
  	bool		showTables = strchr(tabtypes, 't') != NULL;
  	bool		showIndexes = strchr(tabtypes, 'i') != NULL;
  	bool		showViews = strchr(tabtypes, 'v') != NULL;
+ 	bool		showMatViews = strchr(tabtypes, 'm') != NULL;
  	bool		showSeq = strchr(tabtypes, 's') != NULL;
  	bool		showForeign = strchr(tabtypes, 'E') != NULL;
  
***************
*** 2597,2604 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
  	printQueryOpt myopt = pset.popt;
  	static const bool translate_columns[] = {false, false, true, false, false, false, false};
  
! 	if (!(showTables || showIndexes || showViews || showSeq || showForeign))
! 		showTables = showViews = showSeq = showForeign = true;
  
  	initPQExpBuffer(&buf);
  
--- 2625,2632 ----
  	printQueryOpt myopt = pset.popt;
  	static const bool translate_columns[] = {false, false, true, false, false, false, false};
  
! 	if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
! 		showTables = showViews = showMatViews = showSeq = showForeign = true;
  
  	initPQExpBuffer(&buf);
  
***************
*** 2609,2620 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
  	printfPQExpBuffer(&buf,
  					  "SELECT n.nspname as \"%s\",\n"
  					  "  c.relname as \"%s\",\n"
! 					  "  CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
  					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"),
  					  gettext_noop("table"),
  					  gettext_noop("view"),
  					  gettext_noop("index"),
  					  gettext_noop("sequence"),
  					  gettext_noop("special"),
--- 2637,2657 ----
  	printfPQExpBuffer(&buf,
  					  "SELECT n.nspname as \"%s\",\n"
  					  "  c.relname as \"%s\",\n"
! 					  "  CASE c.relkind"
! 					  " WHEN 'r' THEN '%s'"
! 					  " WHEN 'v' THEN '%s'"
! 					  " WHEN 'm' THEN '%s'"
! 					  " WHEN 'i' THEN '%s'"
! 					  " WHEN 'S' THEN '%s'"
! 					  " WHEN 's' THEN '%s'"
! 					  " WHEN 'f' THEN '%s'"
! 					  " END as \"%s\",\n"
  					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
  					  gettext_noop("Schema"),
  					  gettext_noop("Name"),
  					  gettext_noop("table"),
  					  gettext_noop("view"),
+ 					  gettext_noop("materialized view"),
  					  gettext_noop("index"),
  					  gettext_noop("sequence"),
  					  gettext_noop("special"),
***************
*** 2660,2665 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2697,2704 ----
  		appendPQExpBuffer(&buf, "'r',");
  	if (showViews)
  		appendPQExpBuffer(&buf, "'v',");
+ 	if (showMatViews)
+ 		appendPQExpBuffer(&buf, "'m',");
  	if (showIndexes)
  		appendPQExpBuffer(&buf, "'i',");
  	if (showSeq)
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
***************
*** 217,222 **** slashUsage(unsigned short int pager)
--- 217,223 ----
  	fprintf(output, _("  \\di[S+] [PATTERN]      list indexes\n"));
  	fprintf(output, _("  \\dl                    list large objects, same as \\lo_list\n"));
  	fprintf(output, _("  \\dL[S+] [PATTERN]      list procedural languages\n"));
+ 	fprintf(output, _("  \\dm[S+] [PATTERN]      list materialized views\n"));
  	fprintf(output, _("  \\dn[S+] [PATTERN]      list schemas\n"));
  	fprintf(output, _("  \\do[S]  [PATTERN]      list operators\n"));
  	fprintf(output, _("  \\dO[S+] [PATTERN]      list collations\n"));
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 435,445 **** static const SchemaQuery Query_for_list_of_relations = {
  	NULL
  };
  
! static const SchemaQuery Query_for_list_of_tsvf = {
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
! 	"c.relkind IN ('r', 'S', 'v', 'f')",
  	/* viscondition */
  	"pg_catalog.pg_table_is_visible(c.oid)",
  	/* namespace */
--- 435,445 ----
  	NULL
  };
  
! static const SchemaQuery Query_for_list_of_tsvmf = {
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
! 	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
  	/* viscondition */
  	"pg_catalog.pg_table_is_visible(c.oid)",
  	/* namespace */
***************
*** 450,460 **** static const SchemaQuery Query_for_list_of_tsvf = {
  	NULL
  };
  
! static const SchemaQuery Query_for_list_of_tf = {
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
! 	"c.relkind IN ('r', 'f')",
  	/* viscondition */
  	"pg_catalog.pg_table_is_visible(c.oid)",
  	/* namespace */
--- 450,475 ----
  	NULL
  };
  
! static const SchemaQuery Query_for_list_of_tmf = {
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
! 	"c.relkind IN ('r', 'm', 'f')",
! 	/* viscondition */
! 	"pg_catalog.pg_table_is_visible(c.oid)",
! 	/* namespace */
! 	"c.relnamespace",
! 	/* result */
! 	"pg_catalog.quote_ident(c.relname)",
! 	/* qualresult */
! 	NULL
! };
! 
! static const SchemaQuery Query_for_list_of_tm = {
! 	/* catname */
! 	"pg_catalog.pg_class c",
! 	/* selcondition */
! 	"c.relkind IN ('r', 'm')",
  	/* viscondition */
  	"pg_catalog.pg_table_is_visible(c.oid)",
  	/* namespace */
***************
*** 480,485 **** static const SchemaQuery Query_for_list_of_views = {
--- 495,515 ----
  	NULL
  };
  
+ static const SchemaQuery Query_for_list_of_matviews = {
+ 	/* catname */
+ 	"pg_catalog.pg_class c",
+ 	/* selcondition */
+ 	"c.relkind IN ('m')",
+ 	/* viscondition */
+ 	"pg_catalog.pg_table_is_visible(c.oid)",
+ 	/* namespace */
+ 	"c.relnamespace",
+ 	/* result */
+ 	"pg_catalog.quote_ident(c.relname)",
+ 	/* qualresult */
+ 	NULL
+ };
+ 
  
  /*
   * Queries to get lists of names of various kinds of things, possibly
***************
*** 743,748 **** static const pgsql_thing_t words_after_create[] = {
--- 773,779 ----
  	{"GROUP", Query_for_list_of_roles},
  	{"LANGUAGE", Query_for_list_of_languages},
  	{"INDEX", NULL, &Query_for_list_of_indexes},
+ 	{"MATERIALIZED VIEW", NULL, NULL},
  	{"OPERATOR", NULL, NULL},	/* Querying for this is probably not such a
  								 * good idea. */
  	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
***************
*** 924,930 **** psql_completion(char *text, int start, int end)
  		static const char *const list_ALTER[] =
  		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
  			"EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
! 			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR",
  			"ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
  			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
  		"USER", "USER MAPPING FOR", "VIEW", NULL};
--- 955,961 ----
  		static const char *const list_ALTER[] =
  		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
  			"EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
! 			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
  			"ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
  			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
  		"USER", "USER MAPPING FOR", "VIEW", NULL};
***************
*** 1093,1098 **** psql_completion(char *text, int start, int end)
--- 1124,1137 ----
  		COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
  	}
  
+ 	/* ALTER MATERIALIZED VIEW */
+ 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+ 	{
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ 	}
+ 
  	/* ALTER USER,ROLE <name> */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
***************
*** 1259,1264 **** psql_completion(char *text, int start, int end)
--- 1298,1313 ----
  
  		COMPLETE_WITH_LIST(list_ALTERVIEW);
  	}
+ 	/* ALTER MATERIALIZED VIEW <name> */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+ 	{
+ 		static const char *const list_ALTERMATVIEW[] =
+ 		{"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
+ 	}
  	/* ALTER TRIGGER <name>, add ON */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
***************
*** 1717,1730 **** psql_completion(char *text, int start, int end)
  	 */
  	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "UNION SELECT 'VERBOSE'");
  
  	/*
  	 * If the previous words are CLUSTER VERBOSE produce list of tables
  	 */
  	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  
  	/* If we have CLUSTER <sth>, then add "USING" */
  	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
--- 1766,1779 ----
  	 */
  	else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
  
  	/*
  	 * If the previous words are CLUSTER VERBOSE produce list of tables
  	 */
  	else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
  
  	/* If we have CLUSTER <sth>, then add "USING" */
  	else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
***************
*** 1771,1777 **** psql_completion(char *text, int start, int end)
  		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
  			"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
  			"SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
! 			"TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
  			"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
  		"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
  
--- 1820,1826 ----
  		{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
  			"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
  			"SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
! 			"TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
  			"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
  		"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
  
***************
*** 1945,1951 **** psql_completion(char *text, int start, int end)
  			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
  			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
  	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
  			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
--- 1994,2000 ----
  			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
  			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
  	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
  	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
  			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
***************
*** 2043,2049 **** psql_completion(char *text, int start, int end)
  			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
  	{
  		static const char *const list_TEMP[] =
! 		{"SEQUENCE", "TABLE", "VIEW", NULL};
  
  		COMPLETE_WITH_LIST(list_TEMP);
  	}
--- 2092,2098 ----
  			  pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
  	{
  		static const char *const list_TEMP[] =
! 		{"SEQUENCE", "TABLE", "VIEW", "MATERIALIZED VIEW", NULL};
  
  		COMPLETE_WITH_LIST(list_TEMP);
  	}
***************
*** 2051,2057 **** psql_completion(char *text, int start, int end)
  	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
  	{
! 		COMPLETE_WITH_CONST("TABLE");
  	}
  
  /* CREATE TABLESPACE */
--- 2100,2109 ----
  	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
  	{
! 		static const char *const list_UNLOGGED[] =
! 		{"TABLE", "MATERIALIZED VIEW", NULL};
! 
! 		COMPLETE_WITH_LIST(list_UNLOGGED);
  	}
  
  /* CREATE TABLESPACE */
***************
*** 2220,2225 **** psql_completion(char *text, int start, int end)
--- 2272,2290 ----
  			 pg_strcasecmp(prev_wd, "AS") == 0)
  		COMPLETE_WITH_CONST("SELECT");
  
+ /* CREATE MATERIALIZED VIEW */
+ 	/* Complete CREATE MATERIALIZED VIEW <name> with AS */
+ 	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "VIEW") == 0)
+ 		COMPLETE_WITH_CONST("AS");
+ 	/* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
+ 	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+ 			 pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "AS") == 0)
+ 		COMPLETE_WITH_CONST("SELECT");
+ 
  /* DECLARE */
  	else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
  	{
***************
*** 2346,2351 **** psql_completion(char *text, int start, int end)
--- 2411,2425 ----
  
  		COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
  	}
+ 
+ 	/* DROP MATERIALIZED VIEW */
+ 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+ 	{
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ 	}
+ 
  	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
  			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
  			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
***************
*** 2521,2527 **** psql_completion(char *text, int start, int end)
  	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf,
  								   " UNION SELECT 'DATABASE'"
  								   " UNION SELECT 'DOMAIN'"
  								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
--- 2595,2601 ----
  	else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
  								   " UNION SELECT 'DATABASE'"
  								   " UNION SELECT 'DOMAIN'"
  								   " UNION SELECT 'FOREIGN DATA WRAPPER'"
***************
*** 2649,2654 **** psql_completion(char *text, int start, int end)
--- 2723,2736 ----
  			 pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
  		COMPLETE_WITH_CONST("(");
  
+ /* LOAD MATERIALIZED VIEW */
+ 	else if (pg_strcasecmp(prev_wd, "LOAD") == 0)
+ 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
+ 	else if (pg_strcasecmp(prev3_wd, "LOAD") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ 
  /* LOCK */
  	/* Complete LOCK [TABLE] with a list of tables */
  	else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
***************
*** 2751,2757 **** psql_completion(char *text, int start, int end)
  	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
  	{
  		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
! 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
  			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
  		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
--- 2833,2839 ----
  	else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
  	{
  		if (pg_strcasecmp(prev_wd, "TABLE") == 0)
! 			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
  		else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
  			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
  		else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
***************
*** 2783,2791 **** psql_completion(char *text, int start, int end)
  			  pg_strcasecmp(prev_wd, "ON") == 0))
  	{
  		static const char *const list_SECURITY_LABEL[] =
! 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN",
! 			"AGGREGATE", "FUNCTION", "DOMAIN", "LARGE OBJECT",
! 		NULL};
  
  		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
  	}
--- 2865,2873 ----
  			  pg_strcasecmp(prev_wd, "ON") == 0))
  	{
  		static const char *const list_SECURITY_LABEL[] =
! 		{"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
! 			"MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "DOMAIN",
! 			"LARGE OBJECT",	NULL};
  
  		COMPLETE_WITH_LIST(list_SECURITY_LABEL);
  	}
***************
*** 2971,2977 **** psql_completion(char *text, int start, int end)
  
  /* TRUNCATE */
  	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  
  /* UNLISTEN */
  	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
--- 3053,3059 ----
  
  /* TRUNCATE */
  	else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
  
  /* UNLISTEN */
  	else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
***************
*** 3032,3038 **** psql_completion(char *text, int start, int end)
   * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
   */
  	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'FULL'"
  								   " UNION SELECT 'FREEZE'"
  								   " UNION SELECT 'ANALYZE'"
--- 3114,3120 ----
   * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
   */
  	else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'FULL'"
  								   " UNION SELECT 'FREEZE'"
  								   " UNION SELECT 'ANALYZE'"
***************
*** 3040,3073 **** psql_completion(char *text, int start, int end)
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'ANALYZE'"
  								   " UNION SELECT 'VERBOSE'");
  	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
  			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'VERBOSE'");
  	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'ANALYZE'");
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'ANALYZE'");
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
  								   " UNION SELECT 'VERBOSE'");
  	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
  			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
  			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  
  /* WITH [RECURSIVE] */
  
--- 3122,3155 ----
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 (pg_strcasecmp(prev_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'ANALYZE'"
  								   " UNION SELECT 'VERBOSE'");
  	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
  			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'VERBOSE'");
  	else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			 (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
  			  pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'ANALYZE'");
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "VERBOSE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'ANALYZE'");
  	else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
  			 pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
  								   " UNION SELECT 'VERBOSE'");
  	else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
  			  pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
  			 (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
  			  pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
  
  /* WITH [RECURSIVE] */
  
***************
*** 3082,3088 **** psql_completion(char *text, int start, int end)
  /* ANALYZE */
  	/* If the previous word is ANALYZE, produce list of tables */
  	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tf, NULL);
  
  /* WHERE */
  	/* Simple case of the word before the where being the table name */
--- 3164,3170 ----
  /* ANALYZE */
  	/* If the previous word is ANALYZE, produce list of tables */
  	else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
  
  /* WHERE */
  	/* Simple case of the word before the where being the table name */
***************
*** 3094,3104 **** psql_completion(char *text, int start, int end)
  	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
  			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
  			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
  
  /* ... JOIN ... */
  	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
  
  /* Backslash commands */
  /* TODO:  \dc \dd \dl */
--- 3176,3186 ----
  	else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
  			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
  			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
  
  /* ... JOIN ... */
  	else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
  
  /* Backslash commands */
  /* TODO:  \dc \dd \dl */
***************
*** 3138,3144 **** psql_completion(char *text, int start, int end)
  		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
  	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
  			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
  	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
  	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
--- 3220,3226 ----
  		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
  	else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
  			 || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
  	else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
  	else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
***************
*** 3150,3155 **** psql_completion(char *text, int start, int end)
--- 3232,3239 ----
  		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
  	else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+ 	else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
  
  	/* must be at end of \d list */
  	else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
*** a/src/include/catalog/catversion.h
--- b/src/include/catalog/catversion.h
***************
*** 53,58 ****
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201210071
  
  #endif
--- 53,58 ----
   */
  
  /*							yyyymmddN */
! #define CATALOG_VERSION_NO	201211030
  
  #endif
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 66,71 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
--- 66,79 ----
  	bool		relhasrules;	/* has (or has had) any rules */
  	bool		relhastriggers; /* has (or has had) any TRIGGERs */
  	bool		relhassubclass; /* has (or has had) derived classes */
+ 
+ 	/*
+ 	 * When relisvalid is set to false, a query which references the relation
+ 	 * will throw an error saying the relation is not available. The initial
+ 	 * intended use is to flag whether a materialized view has been populated.
+ 	 * It may prove useful for other purposes.
+ 	 */
+ 	bool		relisvalid;		/* is valid for use in queries */
  	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
  
  #ifdef CATALOG_VARLEN			/* variable-length fields start here */
***************
*** 91,97 **** typedef FormData_pg_class *Form_pg_class;
   * ----------------
   */
  
! #define Natts_pg_class					27
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 99,105 ----
   * ----------------
   */
  
! #define Natts_pg_class					28
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
***************
*** 116,124 **** typedef FormData_pg_class *Form_pg_class;
  #define Anum_pg_class_relhasrules		22
  #define Anum_pg_class_relhastriggers	23
  #define Anum_pg_class_relhassubclass	24
! #define Anum_pg_class_relfrozenxid		25
! #define Anum_pg_class_relacl			26
! #define Anum_pg_class_reloptions		27
  
  /* ----------------
   *		initial contents of pg_class
--- 124,133 ----
  #define Anum_pg_class_relhasrules		22
  #define Anum_pg_class_relhastriggers	23
  #define Anum_pg_class_relhassubclass	24
! #define Anum_pg_class_relisvalid		25
! #define Anum_pg_class_relfrozenxid		26
! #define Anum_pg_class_relacl			27
! #define Anum_pg_class_reloptions		28
  
  /* ----------------
   *		initial contents of pg_class
***************
*** 130,142 **** typedef FormData_pg_class *Form_pg_class;
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
  DESCR("");
  
  
--- 139,151 ----
   */
  
  /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 _null_ _null_ ));
  DESCR("");
! DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f t 3 _null_ _null_ ));
  DESCR("");
  
  
***************
*** 147,152 **** DESCR("");
--- 156,162 ----
  #define		  RELKIND_VIEW			  'v'		/* view */
  #define		  RELKIND_COMPOSITE_TYPE  'c'		/* composite type */
  #define		  RELKIND_FOREIGN_TABLE   'f'		/* foreign table */
+ #define		  RELKIND_MATVIEW		  'm'		/* materialized view */
  
  #define		  RELPERSISTENCE_PERMANENT	'p'		/* regular table */
  #define		  RELPERSISTENCE_UNLOGGED	'u'		/* unlogged permanent table */
*** a/src/include/commands/createas.h
--- b/src/include/commands/createas.h
***************
*** 19,24 ****
--- 19,28 ----
  #include "tcop/dest.h"
  
  
+ extern Query *SetupForCreateTableAs(Query *query, IntoClause *into,
+ 									 const char *queryString,
+ 									 ParamListInfo params, DestReceiver *dest);
+ 
  extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
  				  ParamListInfo params, char *completionTag);
  
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 63,74 **** extern void ExplainInitState(ExplainState *es);
  extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
  
  extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
! 				  ExplainState *es,
! 				  const char *queryString, ParamListInfo params);
  
  extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
! 			   ExplainState *es,
! 			   const char *queryString, ParamListInfo params);
  
  extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
  
--- 63,74 ----
  extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
  
  extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
! 				  ExplainState *es, const char *queryString,
! 				  DestReceiver *dest, ParamListInfo params);
  
  extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
! 			   ExplainState *es, const char *queryString,
! 			   DestReceiver *dest, ParamListInfo params);
  
  extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
  
*** /dev/null
--- b/src/include/commands/matview.h
***************
*** 0 ****
--- 1,25 ----
+ /*-------------------------------------------------------------------------
+  *
+  * matview.h
+  *	  prototypes for matview.c.
+  *
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * src/include/commands/matview.h
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef MATVIEW_H
+ #define MATVIEW_H
+ 
+ #include "nodes/params.h"
+ #include "tcop/dest.h"
+ 
+ extern void ExecLoadMatView(LoadMatViewStmt *stmt, const char *queryString,
+ 				  ParamListInfo params, char *completionTag);
+ 
+ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
+ 
+ #endif   /* MATVIEW_H */
*** a/src/include/commands/tablecmds.h
--- b/src/include/commands/tablecmds.h
***************
*** 50,55 **** extern void CheckTableNotInUse(Relation rel, const char *stmt);
--- 50,56 ----
  extern void ExecuteTruncate(TruncateStmt *stmt);
  
  extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
+ extern void SetRelationIsValid(Oid relationId, bool relisvalid);
  
  extern void renameatt(RenameStmt *stmt);
  
***************
*** 78,81 **** extern void AtEOSubXact_on_commit_actions(bool isCommit,
--- 79,84 ----
  extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
  						  Oid relId, Oid oldRelId, void *arg);
  
+ extern bool isQueryUsingTempRelation(Query *query);
+ 
  #endif   /* TABLECMDS_H */
*** a/src/include/commands/view.h
--- b/src/include/commands/view.h
***************
*** 18,21 ****
--- 18,23 ----
  
  extern void DefineView(ViewStmt *stmt, const char *queryString);
  
+ extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
+ 
  #endif   /* VIEW_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 361,366 **** typedef enum NodeTag
--- 361,367 ----
  	T_AlterExtensionContentsStmt,
  	T_CreateEventTrigStmt,
  	T_AlterEventTrigStmt,
+ 	T_LoadMatViewStmt,
  
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1125,1130 **** typedef enum ObjectType
--- 1125,1131 ----
  	OBJECT_INDEX,
  	OBJECT_LANGUAGE,
  	OBJECT_LARGEOBJECT,
+ 	OBJECT_MATVIEW,
  	OBJECT_OPCLASS,
  	OBJECT_OPERATOR,
  	OBJECT_OPFAMILY,
***************
*** 2436,2441 **** typedef struct ExplainStmt
--- 2437,2444 ----
   * A query written as CREATE TABLE AS will produce this node type natively.
   * A query written as SELECT ... INTO will be transformed to this form during
   * parse analysis.
+  * A query written as CREATE MATERIALIZED view will produce this node type,
+  * during parse analysis, since it needs all the same data.
   *
   * The "query" field is handled similarly to EXPLAIN, though note that it
   * can be a SELECT or an EXECUTE, but not other DML statements.
***************
*** 2446,2455 **** typedef struct CreateTableAsStmt
--- 2449,2469 ----
  	NodeTag		type;
  	Node	   *query;			/* the query (see comments above) */
  	IntoClause *into;			/* destination table */
+ 	ObjectType	relkind;		/* type of object */
  	bool		is_select_into; /* it was written as SELECT INTO */
  } CreateTableAsStmt;
  
  /* ----------------------
+  *		LOAD MATERIALIZED VIEW Statement
+  * ----------------------
+  */
+ typedef struct LoadMatViewStmt
+ {
+ 	NodeTag		type;
+ 	RangeVar   *relation;		/* relation to insert into */
+ } LoadMatViewStmt;
+ 
+ /* ----------------------
   * Checkpoint Statement
   * ----------------------
   */
***************
*** 2506,2512 **** typedef struct ConstraintsSetStmt
  typedef struct ReindexStmt
  {
  	NodeTag		type;
! 	ObjectType	kind;			/* OBJECT_INDEX, OBJECT_TABLE, OBJECT_DATABASE */
  	RangeVar   *relation;		/* Table or index to reindex */
  	const char *name;			/* name of database to reindex */
  	bool		do_system;		/* include system tables in database case */
--- 2520,2526 ----
  typedef struct ReindexStmt
  {
  	NodeTag		type;
! 	ObjectType	kind;			/* OBJECT_INDEX, OBJECT_TABLE, etc. */
  	RangeVar   *relation;		/* Table or index to reindex */
  	const char *name;			/* name of database to reindex */
  	bool		do_system;		/* include system tables in database case */
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 80,86 **** typedef struct RangeVar
  } RangeVar;
  
  /*
!  * IntoClause - target information for SELECT INTO and CREATE TABLE AS
   */
  typedef struct IntoClause
  {
--- 80,87 ----
  } RangeVar;
  
  /*
!  * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and
!  * CREATE MATERIALIZED VIEW
   */
  typedef struct IntoClause
  {
***************
*** 92,97 **** typedef struct IntoClause
--- 93,99 ----
  	OnCommitAction onCommit;	/* what do we do at COMMIT? */
  	char	   *tableSpaceName; /* table space to use, or NULL */
  	bool		skipData;		/* true for WITH NO DATA */
+ 	char		relkind;		/* RELKIND_RELATION or RELKIND_MATVIEW */
  } IntoClause;
  
  
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 232,237 **** PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD)
--- 232,238 ----
  PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
  PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+ PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
  PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
  PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
*** a/src/include/tcop/dest.h
--- b/src/include/tcop/dest.h
***************
*** 93,99 **** typedef enum
  	DestTuplestore,				/* results sent to Tuplestore */
  	DestIntoRel,				/* results sent to relation (SELECT INTO) */
  	DestCopyOut,				/* results sent to COPY TO code */
! 	DestSQLFunction				/* results sent to SQL-language func mgr */
  } CommandDest;
  
  /* ----------------
--- 93,100 ----
  	DestTuplestore,				/* results sent to Tuplestore */
  	DestIntoRel,				/* results sent to relation (SELECT INTO) */
  	DestCopyOut,				/* results sent to COPY TO code */
! 	DestSQLFunction,			/* results sent to SQL-language func mgr */
! 	DestTransientRel			/* results sent to transient relation */
  } CommandDest;
  
  /* ----------------
*** a/src/include/tcop/utility.h
--- b/src/include/tcop/utility.h
***************
*** 52,55 **** extern bool CommandIsReadOnly(Node *parsetree);
--- 52,57 ----
  
  extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
  
+ extern bool RelationIsFlaggedAsValid(Oid relid);
+ 
  #endif   /* UTILITY_H */
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 1760,1770 **** plpgsql_parse_cwordtype(List *idents)
  	classStruct = (Form_pg_class) GETSTRUCT(classtup);
  
  	/*
! 	 * It must be a relation, sequence, view, composite type, or foreign table
  	 */
  	if (classStruct->relkind != RELKIND_RELATION &&
  		classStruct->relkind != RELKIND_SEQUENCE &&
  		classStruct->relkind != RELKIND_VIEW &&
  		classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
  		classStruct->relkind != RELKIND_FOREIGN_TABLE)
  		goto done;
--- 1760,1772 ----
  	classStruct = (Form_pg_class) GETSTRUCT(classtup);
  
  	/*
! 	 * It must be a relation, sequence, view, materialized view, composite
! 	 * type, or foreign table
  	 */
  	if (classStruct->relkind != RELKIND_RELATION &&
  		classStruct->relkind != RELKIND_SEQUENCE &&
  		classStruct->relkind != RELKIND_VIEW &&
+ 		classStruct->relkind != RELKIND_MATVIEW &&
  		classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
  		classStruct->relkind != RELKIND_FOREIGN_TABLE)
  		goto done;
***************
*** 1982,1991 **** build_row_from_class(Oid classOid)
  	classStruct = RelationGetForm(rel);
  	relname = RelationGetRelationName(rel);
  
! 	/* accept relation, sequence, view, composite type, or foreign table */
  	if (classStruct->relkind != RELKIND_RELATION &&
  		classStruct->relkind != RELKIND_SEQUENCE &&
  		classStruct->relkind != RELKIND_VIEW &&
  		classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
  		classStruct->relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
--- 1984,1997 ----
  	classStruct = RelationGetForm(rel);
  	relname = RelationGetRelationName(rel);
  
! 	/*
! 	 * Accept relation, sequence, view, materialized view, composite type, or
! 	 * foreign table.
! 	 */
  	if (classStruct->relkind != RELKIND_RELATION &&
  		classStruct->relkind != RELKIND_SEQUENCE &&
  		classStruct->relkind != RELKIND_VIEW &&
+ 		classStruct->relkind != RELKIND_MATVIEW &&
  		classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
  		classStruct->relkind != RELKIND_FOREIGN_TABLE)
  		ereport(ERROR,
*** a/src/pl/tcl/pltcl.c
--- b/src/pl/tcl/pltcl.c
***************
*** 501,506 **** pltcl_init_load_unknown(Tcl_Interp *interp)
--- 501,507 ----
  		return;
  	/* must be table or view, else ignore */
  	if (!(pmrel->rd_rel->relkind == RELKIND_RELATION ||
+ 		  pmrel->rd_rel->relkind == RELKIND_MATVIEW ||
  		  pmrel->rd_rel->relkind == RELKIND_VIEW))
  	{
  		relation_close(pmrel, AccessShareLock);
