Re: Materialized views WIP patch

Started by Kevin Grittneralmost 13 years ago152 messages
#1Kevin Grittner
kgrittn@mail.com
1 attachment(s)

Here is a new version of the patch, with most issues discussed in
previous posts fixed.

I've been struggling with two areas:

- pg_dump sorting for MVs which depend on other MVs
- proper handling of the relisvalid flag for unlogged MVs after recovery

I've been hacking at the code in those areas without success;
what's here is the least broken form I have, but work is still
needed for these cases. Any other problems are news to me.

In addition, the docs need another pass, and there is an open
question about what is the right thing to use for TRUNCATE syntax.

-Kevin

Attachments:

matview-v2.patchtext/x-patch; charset=utf-8; name=matview-v2.patchDownload
*** 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
***************
*** 282,288 **** 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 "
--- 282,288 ----
  			 "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
***************
*** 457,464 **** 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);
  
--- 457,464 ----
  		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
***************
*** 1597,1604 ****
     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.
--- 1597,1604 ----
     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.
***************
*** 1789,1796 ****
        <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>
--- 1789,1797 ----
        <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>
***************
*** 1863,1868 ****
--- 1864,1879 ----
       </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">
***************
*** 135,140 **** Complete list of usable sgml source files in this directory.
--- 138,144 ----
  <!ENTITY prepare            SYSTEM "prepare.sgml">
  <!ENTITY prepareTransaction SYSTEM "prepare_transaction.sgml">
  <!ENTITY reassignOwned      SYSTEM "reassign_owned.sgml">
+ <!ENTITY refreshMaterializedView SYSTEM "refresh_materialized_view.sgml">
  <!ENTITY reindex            SYSTEM "reindex.sgml">
  <!ENTITY releaseSavepoint   SYSTEM "release_savepoint.sgml">
  <!ENTITY reset              SYSTEM "reset.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-refreshmaterializedview"></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,174 ----
+ <!--
+ 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 [ 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 ]
+     [ 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>REFRESH 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>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>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.
+      </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>REFRESH
+       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-refreshmaterializedview"></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
***************
*** 352,357 **** CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c
--- 352,358 ----
    <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-refreshmaterializedview"></member>
+   </simplelist>
+  </refsect1>
+ 
+ </refentry>
*** /dev/null
--- b/doc/src/sgml/ref/refresh_materialized_view.sgml
***************
*** 0 ****
--- 1,83 ----
+ <!--
+ doc/src/sgml/ref/refresh_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+ 
+ <refentry id="SQL-REFRESHMATERIALIZEDVIEW">
+  <refmeta>
+   <refentrytitle>REFRESH MATERIALIZED VIEW</refentrytitle>
+   <manvolnum>7</manvolnum>
+   <refmiscinfo>SQL - Language Statements</refmiscinfo>
+  </refmeta>
+ 
+  <refnamediv>
+   <refname>REFRESH MATERIALIZED VIEW</refname>
+   <refpurpose>refresh a materialized view</refpurpose>
+  </refnamediv>
+ 
+  <indexterm zone="sql-dropmaterializedview">
+   <primary>REFRESH MATERIALIZED VIEW</primary>
+  </indexterm>
+ 
+  <refsynopsisdiv>
+ <synopsis>
+ REFRESH MATERIALIZED VIEW <replaceable class="PARAMETER">name</replaceable>
+ </synopsis>
+  </refsynopsisdiv>
+ 
+  <refsect1>
+   <title>Description</title>
+ 
+   <para>
+    <command>REFRESH 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>
+ REFRESH MATERIALIZED VIEW order_summary;
+ </programlisting></para>
+  </refsect1>
+ 
+  <refsect1>
+   <title>Compatibility</title>
+ 
+   <para>
+    <command>REFRESH 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;
***************
*** 163,168 ****
--- 166,172 ----
     &prepare;
     &prepareTransaction;
     &reassignOwned;
+    &refreshMaterializedView;
     &reindex;
     &releaseSavepoint;
     &reset;
*** 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
***************
*** 2112,2118 **** 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));
--- 2112,2119 ----
  	 * 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));
***************
*** 2657,2663 **** 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));
--- 2658,2665 ----
  	 * 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));
***************
*** 3016,3022 **** 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));
--- 3018,3025 ----
  	 * 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
***************
*** 3024,3029 **** getRelationDescription(StringInfo buffer, Oid relid)
--- 3024,3033 ----
  			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
***************
*** 778,783 **** InsertPgClassTuple(Relation pg_class_desc,
--- 778,784 ----
  	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;
***************
*** 833,838 **** AddNewRelationTuple(Relation pg_class_desc,
--- 834,840 ----
  	switch (relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_INDEX:
  		case RELKIND_TOASTVALUE:
  			/* The relation is real, but as yet empty */
***************
*** 856,861 **** AddNewRelationTuple(Relation pg_class_desc,
--- 858,864 ----
  
  	/* Initialize relfrozenxid */
  	if (relkind == RELKIND_RELATION ||
+ 		relkind == RELKIND_MATVIEW ||
  		relkind == RELKIND_TOASTVALUE)
  	{
  		/*
***************
*** 879,884 **** AddNewRelationTuple(Relation pg_class_desc,
--- 882,888 ----
  	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;
  
***************
*** 1057,1064 **** 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;
--- 1061,1068 ----
  		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;
***************
*** 1084,1089 **** heap_create_with_catalog(const char *relname,
--- 1088,1094 ----
  		{
  			case RELKIND_RELATION:
  			case RELKIND_VIEW:
+ 			case RELKIND_MATVIEW:
  			case RELKIND_FOREIGN_TABLE:
  				relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
  											  relnamespace);
***************
*** 1127,1132 **** heap_create_with_catalog(const char *relname,
--- 1132,1138 ----
  	 */
  	if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
  							  relkind == RELKIND_VIEW ||
+ 							  relkind == RELKIND_MATVIEW ||
  							  relkind == RELKIND_FOREIGN_TABLE ||
  							  relkind == RELKIND_COMPOSITE_TYPE))
  		new_array_oid = AssignTypeArrayOid();
***************
*** 1304,1310 **** 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);
  	}
  
--- 1310,1317 ----
  
  	if (relpersistence == RELPERSISTENCE_UNLOGGED)
  	{
! 		Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
! 			   relkind == RELKIND_TOASTVALUE);
  		heap_create_init_fork(new_rel_desc);
  	}
  
***************
*** 1330,1341 **** heap_create_init_fork(Relation rel)
--- 1337,1396 ----
  {
  	RelationOpenSmgr(rel);
  	smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
+ 
+ 	/*
+ 	 * Create a special init fork for a materialized view, so that on its
+ 	 * initial reference it can be flagged as invalid.
+ 	 */
+ 	if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ 	{
+ 		Page		page;
+ 
+ 		page = (Page) palloc(BLCKSZ);
+ 		PageInit(page, BLCKSZ, 1);
+ 		smgrextend(rel->rd_smgr, INIT_FORKNUM, 0, (char *) page, true);
+ 	}
+ 
  	if (XLogIsNeeded())
  		log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
  	smgrimmedsync(rel->rd_smgr, INIT_FORKNUM);
  }
  
  /*
+  * Check whether the first page of a materialized view looks like it came from
+  * its init fork, which would mean that the MV should be flagged as invalid
+  * for querying.
+  *
+  * The check here must match what is set up in heap_create_init_fork().
+  */
+ bool
+ heap_is_matview_init_fork(Relation rel)
+ {
+ 	Page		page;
+ 	bool		isInitFork;
+ 
+ 	Assert(rel->rd_rel->relkind == RELKIND_MATVIEW);
+ 
+ 	RelationOpenSmgr(rel);
+ 	if (!smgrexists(rel->rd_smgr, MAIN_FORKNUM))
+ 		return false;
+ 	if (smgrnblocks(rel->rd_smgr, MAIN_FORKNUM) < 1)
+ 		return false;
+ 	page = (Page) palloc(BLCKSZ);
+ 	smgrread(rel->rd_smgr, MAIN_FORKNUM, 0, (char *) page);
+ 	isInitFork = (((PageHeader) page)->pd_special > 0 &&
+ 				  ((PageHeader) page)->pd_special < BLCKSZ);
+ 	pfree(page);
+ 
+ 	if (isInitFork)
+ 	{
+ 		smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, 0);
+ 	}
+ 
+ 	return isInitFork;
+ }
+ 
+ /*
   *		RelationRemoveInheritance
   *
   * Formerly, this routine checked for child relations and aborted the
*** 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
***************
*** 112,117 **** ExecRenameStmt(RenameStmt *stmt)
--- 112,118 ----
  		case OBJECT_TABLE:
  		case OBJECT_SEQUENCE:
  		case OBJECT_VIEW:
+ 		case OBJECT_MATVIEW:
  		case OBJECT_INDEX:
  		case OBJECT_FOREIGN_TABLE:
  			return RenameRelation(stmt);
***************
*** 162,167 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
--- 163,169 ----
  		case OBJECT_SEQUENCE:
  		case OBJECT_TABLE:
  		case OBJECT_VIEW:
+ 		case OBJECT_MATVIEW:
  			return AlterTableNamespace(stmt);
  
  		case OBJECT_DOMAIN:
*** 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
***************
*** 1428,1433 **** BeginCopyTo(Relation rel,
--- 1428,1439 ----
  					 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),
***************
*** 1935,1940 **** CopyFrom(CopyState cstate)
--- 1941,1951 ----
  					(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,122 ----
  
  
  /*
+  * Common setup needed by both normal execution and EXPLAIN ANALYZE.
+  */
+ Query *
+ SetupForCreateTableAs(Query *query, IntoClause *into, const char *queryString,
+ 					   ParamListInfo params, DestReceiver *dest)
+ {
+ 	List       *rewritten;
+ 
+ 	Assert(query->commandType == CMD_SELECT);
+ 
+ 	if (into->relkind == RELKIND_MATVIEW)
+ 		query = (Query *) parse_analyze((Node *) copyObject(query),
+ 										 queryString, NULL, 0)->utilityStmt;
+ 
+ 	/*
+ 	 * 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(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")));
+ 	}
+ 
+ 	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;
--- 126,131 ----
***************
*** 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);
--- 149,156 ----
  
  		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
--- 340,377 ----
  	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")));
! 
! 		/*
! 		 * Check whether any temporary database objects are used in the
! 		 * creation query. It would be hard to refresh data or incrementally
! 		 * maintain it if a source disappeared.
! 		 */
! 		if (isQueryUsingTempRelation(myState->query))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("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)
--- 397,415 ----
  	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++)
--- 418,425 ----
  	rte = makeNode(RangeTblEntry);
  	rte->rtekind = RTE_RELATION;
  	rte->relid = intoRelationId;
! 	rte->relkind = into->relkind;
! 	rte->isResultRel = true;
  	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, "REFRESH 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
***************
*** 355,361 **** DefineIndex(IndexStmt *stmt,
  	relationId = RelationGetRelid(rel);
  	namespaceId = RelationGetNamespace(rel);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  	{
  		if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  
--- 355,362 ----
  	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)
  
***************
*** 1835,1841 **** 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 */
--- 1836,1843 ----
  	{
  		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,322 ----
+ /*-------------------------------------------------------------------------
+  *
+  * 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 refresh_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ 						  Query *dataQuery, const char *queryString);
+ 
+ /*
+  * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
+  */
+ void
+ ExecRefreshMatView(RefreshMatViewStmt *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));
+ 
+ 	/*
+ 	 * Check that everything is correct for a refresh. 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.
+ 	 *
+ 	 * NB: We count on this to protect us against problems with refreshing the
+ 	 * data using HEAP_INSERT_FROZEN.
+ 	 */
+ 	CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
+ 
+ 	tableSpace = matviewRel->rd_rel->reltablespace;
+ 	isWithOids = matviewRel->rd_rel->relhasoids;
+ 
+ 	heap_close(matviewRel, NoLock);
+ 
+ 	refresh_matview(matviewOid, tableSpace, isWithOids, dataQuery, queryString);
+ }
+ 
+ /*
+  * refresh_matview
+  *
+  * This refreshes 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
+ refresh_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ 			 Query *dataQuery, const char *queryString)
+ {
+ 	Oid			OIDNewHeap;
+ 	PlannedStmt *plan;
+ 	DestReceiver *dest;
+ 	QueryDesc  *queryDesc;
+ 	List	   *rtable;
+ 
+ 	/* Check for user-requested abort. */
+ 	CHECK_FOR_INTERRUPTS();
+ 
+ 	/*
+ 	 * Kludge here to allow refresh of a materialized view which is invalid
+ 	 * (that is, it was created WITH NO DATA or was TRUNCATED). We flag the
+ 	 * first two RangeTblEntry list elements, which were added to the front
+ 	 * of the rewritten Query to keep the rules system happy, with the
+ 	 * isResultRel flag to indicate that it is OK if they are flagged as
+ 	 * invalid.
+ 	 */
+ 	rtable = dataQuery->rtable;
+ 	((RangeTblEntry *) linitial(rtable))->isResultRel = true;
+ 	((RangeTblEntry *) lsecond(rtable))->isResultRel = true;
+ 
+ 	/* Plan the query which will generate data for the refresh. */
+ 	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 | HEAP_INSERT_FROZEN;
+ 	if (!XLogIsNeeded())
+ 		myState->hi_options |= 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)
***************
*** 785,790 **** RemoveRelations(DropStmt *drop)
--- 794,803 ----
  			relkind = RELKIND_VIEW;
  			break;
  
+ 		case OBJECT_MATVIEW:
+ 			relkind = RELKIND_MATVIEW;
+ 			break;
+ 
  		case OBJECT_FOREIGN_TABLE:
  			relkind = RELKIND_FOREIGN_TABLE;
  			break;
***************
*** 1153,1158 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1166,1178 ----
  			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.
  			 */
***************
*** 1216,1226 **** 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 */
--- 1236,1247 ----
  {
  	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 */
***************
*** 2041,2046 **** SetRelationHasSubclass(Oid relationId, bool relhassubclass)
--- 2062,2100 ----
  }
  
  /*
+  * 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.
+  */
+ 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;
+ 		heap_inplace_update(relationRelation, tuple);
+ 	}
+ 
+ 	heap_freetuple(tuple);
+ 	heap_close(relationRelation, RowExclusiveLock);
+ }
+ 
+ /*
   *		renameatt_check			- basic sanity checks before attribute rename
   */
  static void
***************
*** 2062,2073 **** 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))));
  
  	/*
--- 2116,2128 ----
  	 */
  	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))));
  
  	/*
***************
*** 2984,2995 **** 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;
--- 3039,3050 ----
  			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;
***************
*** 3002,3008 **** 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;
--- 3057,3063 ----
  			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;
***************
*** 3049,3055 **** 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;
--- 3104,3110 ----
  			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;
***************
*** 3076,3082 **** 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 */
--- 3131,3137 ----
  			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 */
***************
*** 3084,3090 **** 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;
--- 3139,3145 ----
  		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;
***************
*** 3197,3203 **** ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  	{
  		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  
! 		if (tab->relkind == RELKIND_RELATION)
  			AlterTableCreateToastTable(tab->relid, (Datum) 0);
  	}
  }
--- 3252,3259 ----
  	{
  		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  
! 		if (tab->relkind == RELKIND_RELATION ||
! 			tab->relkind == RELKIND_MATVIEW)
  			AlterTableCreateToastTable(tab->relid, (Datum) 0);
  	}
  }
***************
*** 3929,3934 **** ATSimplePermissions(Relation rel, int allowed_targets)
--- 3985,3993 ----
  		case RELKIND_VIEW:
  			actual_target = ATT_VIEW;
  			break;
+ 		case RELKIND_MATVIEW:
+ 			actual_target = ATT_MATVIEW;
+ 			break;
  		case RELKIND_INDEX:
  			actual_target = ATT_INDEX;
  			break;
***************
*** 3975,3992 **** 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;
--- 4034,4060 ----
  		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;
***************
*** 4139,4145 **** 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,
--- 4207,4214 ----
  		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,
***************
*** 4967,4977 **** 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 */
--- 5036,5047 ----
  	 * 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 */
***************
*** 8078,8083 **** 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;
***************
*** 8234,8244 **** 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;
***************
*** 8254,8260 **** 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)
***************
*** 8524,8529 **** 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:
***************
*** 8532,8538 **** 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;
  	}
***************
*** 9815,9822 **** 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,
--- 9889,9897 ----
  }
  
  /*
!  * 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,
***************
*** 9837,9843 **** 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,
--- 9912,9919 ----
  							   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,
***************
*** 10242,10251 **** 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,
--- 10318,10328 ----
  
  /*
   * 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 REFRESH MATERIALIZED VIEW; we expose it here so that it can be
!  * used by all.
   */
  void
  RangeVarCallbackOwnsTable(const RangeVar *relation,
***************
*** 10265,10274 **** 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()))
--- 10342,10352 ----
  	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()))
***************
*** 10350,10355 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10428,10438 ----
  				(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),
***************
*** 10386,10394 **** 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",
--- 10469,10477 ----
  	 * 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",
***************
*** 10396,10398 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10479,10529 ----
  
  	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
***************
*** 2781,2787 **** 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;
--- 2781,2788 ----
  												 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,495 ----
  	 */
  	CommandCounterIncrement();
  
+ 	StoreViewQuery(viewOid, viewParse, stmt->replace);
+ 
+ 	return viewOid;
+ }
+ 
+ /*
+  * 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,545 **** DefineView(ViewStmt *stmt, const char *queryString)
  	/*
  	 * Now create the rules associated with the view.
  	 */
! 	DefineViewRules(viewOid, viewParse, stmt->replace);
! 
! 	return viewOid;
  }
--- 499,503 ----
  	/*
  	 * 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,536 ----
  
  
  /*
+  * 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)
+ 			{
+ 				/* It is OK to replace the contents of an invalid matview. */
+ 				if (rte->isResultRel)
+ 					continue;
+ 
+ 				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 REFRESH 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)
--- 765,775 ----
  	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;
***************
*** 990,995 **** CheckValidResultRel(Relation resultRel, CmdType operation)
--- 1036,1047 ----
  					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),
***************
*** 1040,1045 **** CheckValidRowMarkRel(Relation rel, RowMarkType markType)
--- 1092,1104 ----
  					 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
***************
*** 2050,2055 **** _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
--- 2050,2062 ----
  					if (((CreateTableAsStmt *) stmt)->is_select_into)
  						res = SPI_OK_SELINTO;
  				}
+ 				else if (IsA(stmt, RefreshMatViewStmt))
+ 				{
+ 					Assert(strncmp(completionTag,
+ 								   "REFRESH 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;
  }
***************
*** 1969,1974 **** _copyRangeTblEntry(const RangeTblEntry *from)
--- 1970,1976 ----
  	COPY_SCALAR_FIELD(rtekind);
  	COPY_SCALAR_FIELD(relid);
  	COPY_SCALAR_FIELD(relkind);
+ 	COPY_SCALAR_FIELD(isResultRel);
  	COPY_NODE_FIELD(subquery);
  	COPY_SCALAR_FIELD(security_barrier);
  	COPY_SCALAR_FIELD(jointype);
***************
*** 3226,3236 **** _copyCreateTableAsStmt(const CreateTableAsStmt *from)
--- 3228,3249 ----
  
  	COPY_NODE_FIELD(query);
  	COPY_NODE_FIELD(into);
+ 	COPY_SCALAR_FIELD(relkind);
  	COPY_SCALAR_FIELD(is_select_into);
  
  	return newnode;
  }
  
+ static RefreshMatViewStmt *
+ _copyRefreshMatViewStmt(const RefreshMatViewStmt *from)
+ {
+ 	RefreshMatViewStmt *newnode = makeNode(RefreshMatViewStmt);
+ 
+ 	COPY_NODE_FIELD(relation);
+ 
+ 	return newnode;
+ }
+ 
  static CreateSeqStmt *
  _copyCreateSeqStmt(const CreateSeqStmt *from)
  {
***************
*** 4301,4306 **** copyObject(const void *from)
--- 4314,4322 ----
  		case T_CreateTableAsStmt:
  			retval = _copyCreateTableAsStmt(from);
  			break;
+ 		case T_RefreshMatViewStmt:
+ 			retval = _copyRefreshMatViewStmt(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
+ _equalRefreshMatViewStmt(const RefreshMatViewStmt *a, const RefreshMatViewStmt *b)
+ {
+ 	COMPARE_NODE_FIELD(relation);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
  {
  	COMPARE_NODE_FIELD(sequence);
***************
*** 2221,2226 **** _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
--- 2231,2237 ----
  	COMPARE_SCALAR_FIELD(rtekind);
  	COMPARE_SCALAR_FIELD(relid);
  	COMPARE_SCALAR_FIELD(relkind);
+ 	COMPARE_SCALAR_FIELD(isResultRel);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_SCALAR_FIELD(security_barrier);
  	COMPARE_SCALAR_FIELD(jointype);
***************
*** 2788,2793 **** equal(const void *a, const void *b)
--- 2799,2807 ----
  		case T_CreateTableAsStmt:
  			retval = _equalCreateTableAsStmt(a, b);
  			break;
+ 		case T_RefreshMatViewStmt:
+ 			retval = _equalRefreshMatViewStmt(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
***************
*** 2350,2355 **** _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
--- 2351,2357 ----
  		case RTE_RELATION:
  			WRITE_OID_FIELD(relid);
  			WRITE_CHAR_FIELD(relkind);
+ 			WRITE_BOOL_FIELD(isResultRel);
  			break;
  		case RTE_SUBQUERY:
  			WRITE_NODE_FIELD(subquery);
*** 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();
  }
***************
*** 1189,1194 **** _readRangeTblEntry(void)
--- 1190,1196 ----
  		case RTE_RELATION:
  			READ_OID_FIELD(relid);
  			READ_CHAR_FIELD(relkind);
+ 			READ_BOOL_FIELD(isResultRel);
  			break;
  		case RTE_SUBQUERY:
  			READ_NODE_FIELD(subquery);
*** 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
***************
*** 409,414 **** estimate_rel_size(Relation rel, int32 *attr_widths,
--- 409,415 ----
  	{
  		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_RefreshMatViewStmt:
+ 			/* 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 RefreshMatViewStmt
  
  %type <node>	select_no_parens select_with_parens select_clause
  				simple_select values_clause
***************
*** 350,356 **** static void processCASbits(int cas_bits, int location, const char *constrType,
  %type <defelt>	fdw_option
  
  %type <range>	OptTempTableName
! %type <into>	into_clause create_as_target
  
  %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
  %type <fun_param> func_arg func_arg_with_default table_func_column
--- 358,364 ----
  %type <defelt>	fdw_option
  
  %type <range>	OptTempTableName
! %type <into>	into_clause create_as_target create_mv_target
  
  %type <defelt>	createfunc_opt_item common_func_opt_item dostmt_opt_item
  %type <fun_param> func_arg func_arg_with_default table_func_column
***************
*** 359,364 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 367,373 ----
  
  %type <boolean>  opt_trusted opt_restart_seqs
  %type <ival>	 OptTemp
+ %type <ival>	 OptNoLog
  %type <oncommit> OnCommitOption
  
  %type <node>	for_locking_item
***************
*** 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
--- 564,570 ----
  	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
***************
*** 570,576 **** static void processCASbits(int cas_bits, int location, const char *constrType,
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
--- 579,585 ----
  
  	QUOTE
  
! 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
  	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
  	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
  	ROW ROWS RULE
***************
*** 743,748 **** stmt :
--- 752,758 ----
  			| CreateForeignTableStmt
  			| CreateFunctionStmt
  			| CreateGroupStmt
+ 			| CreateMatViewStmt
  			| CreateOpClassStmt
  			| CreateOpFamilyStmt
  			| AlterOpFamilyStmt
***************
*** 788,793 **** stmt :
--- 798,804 ----
  			| IndexStmt
  			| InsertStmt
  			| ListenStmt
+ 			| RefreshMatViewStmt
  			| 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.
   *****************************************************************************/
  
--- 1705,1713 ----
  
  /*****************************************************************************
   *
!  *	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:
--- 1784,1807 ----
  					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:
***************
*** 3153,3158 **** CreateAsStmt:
--- 3182,3188 ----
  					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;
***************
*** 3171,3176 **** create_as_target:
--- 3201,3207 ----
  					$$->onCommit = $4;
  					$$->tableSpaceName = $5;
  					$$->skipData = false;		/* might get changed later */
+ 					$$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
  				}
  		;
  
***************
*** 3184,3189 **** opt_with_data:
--- 3215,3278 ----
  /*****************************************************************************
   *
   *		QUERY :
+  *				CREATE MATERIALIZED VIEW relname AS SelectStmt
+  *
+  *****************************************************************************/
+ 
+ CreateMatViewStmt:
+ 		CREATE OptNoLog MATERIALIZED VIEW create_mv_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;
+ 				}
+ 		;
+ 
+ create_mv_target:
+ 			qualified_name opt_column_list OptWith OptTableSpace
+ 				{
+ 					$$ = makeNode(IntoClause);
+ 					$$->rel = $1;
+ 					$$->colNames = $2;
+ 					$$->options = $3;
+ 					$$->onCommit = ONCOMMIT_NOOP;
+ 					$$->tableSpaceName = $4;
+ 					$$->skipData = false;		/* might get changed later */
+ 					$$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
+ 				}
+ 		;
+ 
+ OptNoLog:	UNLOGGED					{ $$ = RELPERSISTENCE_UNLOGGED; }
+ 			| /*EMPTY*/					{ $$ = RELPERSISTENCE_PERMANENT; }
+ 		;
+ 
+ 
+ /*****************************************************************************
+  *
+  *		QUERY :
+  *				REFRESH MATERIALIZED VIEW qualified_name
+  *
+  *****************************************************************************/
+ 
+ RefreshMatViewStmt:
+ 		REFRESH MATERIALIZED VIEW qualified_name
+ 				{
+ 					RefreshMatViewStmt *n = makeNode(RefreshMatViewStmt);
+ 					n->relation = $4;
+ 					$$ = (Node *) n;
+ 				}
+ 		;
+ 
+ 
+ /*****************************************************************************
+  *
+  *		QUERY :
   *				CREATE SEQUENCE seqname
   *				ALTER SEQUENCE seqname
   *
***************
*** 3698,3703 **** AlterExtensionContentsStmt:
--- 3787,3801 ----
  					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);
***************
*** 5024,5029 **** DropStmt:	DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior
--- 5122,5128 ----
  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; }
***************
*** 5090,5096 **** 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) |
--- 5189,5196 ----
   *				   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) |
***************
*** 5264,5269 **** comment_type:
--- 5364,5370 ----
  			| 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; }
***************
*** 5365,5370 **** security_label_type:
--- 5466,5472 ----
  			| TABLESPACE						{ $$ = OBJECT_TABLESPACE; }
  			| TYPE_P							{ $$ = OBJECT_TYPE; }
  			| VIEW								{ $$ = OBJECT_VIEW; }
+ 			| MATERIALIZED VIEW					{ $$ = OBJECT_MATVIEW; }
  		;
  
  security_label:	Sconst				{ $$ = $1; }
***************
*** 6907,6912 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 7009,7034 ----
  					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);
***************
*** 7314,7319 **** AlterObjectSchemaStmt:
--- 7436,7459 ----
  					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);
***************
*** 8468,8473 **** ExplainableStmt:
--- 8608,8615 ----
  			| DeleteStmt
  			| DeclareCursorStmt
  			| CreateAsStmt
+ 			| CreateMatViewStmt
+ 			| RefreshMatViewStmt
  			| ExecuteStmt					/* by default all are $$=$1 */
  		;
  
***************
*** 8552,8557 **** ExecuteStmt: EXECUTE name execute_param_clause
--- 8694,8700 ----
  					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;
***************
*** 9098,9103 **** into_clause:
--- 9241,9247 ----
  					$$->onCommit = ONCOMMIT_NOOP;
  					$$->tableSpaceName = NULL;
  					$$->skipData = false;
+ 					$$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
  				}
  			| /*EMPTY*/
  				{ $$ = NULL; }
***************
*** 12562,12567 **** unreserved_keyword:
--- 12706,12712 ----
  			| LOCK_P
  			| MAPPING
  			| MATCH
+ 			| MATERIALIZED
  			| MAXVALUE
  			| MINUTE_P
  			| MINVALUE
***************
*** 12606,12611 **** unreserved_keyword:
--- 12751,12757 ----
  			| RECHECK
  			| RECURSIVE
  			| REF
+ 			| REFRESH
  			| REINDEX
  			| RELATIVE_P
  			| RELEASE
*** 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
***************
*** 257,262 **** DefineQueryRewrite(char *rulename,
--- 257,263 ----
  	 * 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),
***************
*** 353,359 **** DefineQueryRewrite(char *rulename,
  		 */
  		checkRuleResultList(query->targetList,
  							RelationGetDescr(event_relation),
! 							true);
  
  		/*
  		 * ... there must not be another ON SELECT rule already ...
--- 354,361 ----
  		 */
  		checkRuleResultList(query->targetList,
  							RelationGetDescr(event_relation),
! 							event_relation->rd_rel->relkind !=
! 								RELKIND_MATVIEW);
  
  		/*
  		 * ... there must not be another ON SELECT rule already ...
***************
*** 411,417 **** 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;
  
--- 413,420 ----
  		 * 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_RefreshMatViewStmt:
  		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;
***************
*** 1137,1142 **** standard_ProcessUtility(Node *parsetree,
--- 1161,1173 ----
  							  queryString, params, completionTag);
  			break;
  
+ 		case T_RefreshMatViewStmt:
+ 			if (isCompleteQuery)
+ 				EventTriggerDDLCommandStart(parsetree);
+ 			ExecRefreshMatView((RefreshMatViewStmt *) parsetree,
+ 								queryString, params, completionTag);
+ 			break;
+ 
  		case T_VariableSetStmt:
  			ExecSetVariableStmt((VariableSetStmt *) parsetree);
  			break;
***************
*** 1263,1268 **** standard_ProcessUtility(Node *parsetree,
--- 1294,1300 ----
  						ReindexIndex(stmt->relation);
  						break;
  					case OBJECT_TABLE:
+ 					case OBJECT_MATVIEW:
  						ReindexTable(stmt->relation);
  						break;
  					case OBJECT_DATABASE:
***************
*** 1482,1490 **** 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)
--- 1514,1523 ----
   * 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)
***************
*** 1628,1633 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1661,1669 ----
  		case OBJECT_VIEW:
  			tag = "ALTER VIEW";
  			break;
+ 		case OBJECT_MATVIEW:
+ 			tag = "ALTER MATERIALIZED VIEW";
+ 			break;
  		default:
  			tag = "???";
  			break;
***************
*** 1825,1830 **** CreateCommandTag(Node *parsetree)
--- 1861,1869 ----
  				case OBJECT_VIEW:
  					tag = "DROP VIEW";
  					break;
+ 				case OBJECT_MATVIEW:
+ 					tag = "DROP MATERIALIZED VIEW";
+ 					break;
  				case OBJECT_INDEX:
  					tag = "DROP INDEX";
  					break;
***************
*** 2086,2095 **** 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:
--- 2125,2148 ----
  			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_RefreshMatViewStmt:
! 			tag = "REFRESH MATERIALIZED VIEW";
  			break;
  
  		case T_VariableSetStmt:
***************
*** 2622,2627 **** GetCommandLogLevel(Node *parsetree)
--- 2675,2684 ----
  			lev = LOGSTMT_DDL;
  			break;
  
+ 		case T_RefreshMatViewStmt:
+ 			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
***************
*** 718,723 **** pg_relation_filenode(PG_FUNCTION_ARGS)
--- 718,724 ----
  	switch (relform->relkind)
  	{
  		case RELKIND_RELATION:
+ 		case RELKIND_MATVIEW:
  		case RELKIND_INDEX:
  		case RELKIND_SEQUENCE:
  		case RELKIND_TOASTVALUE:
***************
*** 766,771 **** pg_relation_filepath(PG_FUNCTION_ARGS)
--- 767,773 ----
  	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
***************
*** 36,41 ****
--- 36,42 ----
  #include "access/htup_details.h"
  #include "access/xact.h"
  #include "catalog/catalog.h"
+ #include "catalog/heap.h"
  #include "catalog/index.h"
  #include "catalog/indexing.h"
  #include "catalog/namespace.h"
***************
*** 54,59 ****
--- 55,61 ----
  #include "catalog/pg_type.h"
  #include "catalog/schemapg.h"
  #include "catalog/storage.h"
+ #include "commands/tablecmds.h"
  #include "commands/trigger.h"
  #include "miscadmin.h"
  #include "optimizer/clauses.h"
***************
*** 376,381 **** RelationParseRelOptions(Relation relation, HeapTuple tuple)
--- 378,384 ----
  		case RELKIND_TOASTVALUE:
  		case RELKIND_INDEX:
  		case RELKIND_VIEW:
+ 		case RELKIND_MATVIEW:
  			break;
  		default:
  			return;
***************
*** 942,947 **** RelationBuildDesc(Oid targetRelId, bool insertIt)
--- 945,967 ----
  	if (insertIt)
  		RelationCacheInsert(relation);
  
+ 	/* flag unlogged matview invalid if its heap looks like the init fork */
+ 	if (insertIt &&
+ 		relation->rd_rel->relkind == RELKIND_MATVIEW &&
+ 		relation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ 	{
+ 		/* prevent race conditions on this part of the test */
+ 		LWLockAcquire(UnloggedMatViewInitLock, LW_EXCLUSIVE);
+ 		if (relation->rd_rel->relisvalid &&
+ 			heap_is_matview_init_fork(relation))
+ 		{
+ 			SetRelationIsValid(relid, false);
+ 			RelationOpenSmgr(relation);
+ 			smgrtruncate(relation->rd_smgr, MAIN_FORKNUM, 0);
+ 		}
+ 		LWLockRelease(UnloggedMatViewInitLock);
+ 	}
+ 
  	/* It's fully valid */
  	relation->rd_isvalid = true;
  
***************
*** 1432,1437 **** formrdesc(const char *relationName, Oid relationReltype,
--- 1452,1458 ----
  	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;
  
***************
*** 2555,2560 **** RelationBuildLocalRelation(const char *relname,
--- 2576,2582 ----
  	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
***************
*** 2076,2082 **** 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",
--- 2076,2082 ----
  	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,1649 ----
  }
  
  /*
+  * refreshMatViewData -
+  *	  load or refresh the contents of a single materialized view
+  *
+  * Actually, this just makes an ArchiveEntry for the REFRESH MATERIALIZED VIEW
+  * statement.
+  */
+ static void
+ refreshMatViewData(Archive *fout, TableDataInfo *tdinfo)
+ {
+ 	TableInfo  *tbinfo = tdinfo->tdtable;
+ 	PQExpBuffer q;
+ 
+ 	q = createPQExpBuffer();
+ 	appendPQExpBuffer(q, "REFRESH 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
   */
***************
*** 1655,1664 **** makeTableDataInfo(TableInfo *tbinfo, bool oids)
  							   tbinfo->dobj.catId.oid))
  		return;
  
  	/* 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
--- 1694,1710 ----
  							   tbinfo->dobj.catId.oid))
  		return;
  
+ 	/* An invalid materialized view does not generate data. */
+ 	if (!(tbinfo->relisvalid))
+ 		return;
+ 
  	/* OK, let's dump it */
  	tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
  
! 	if (tbinfo->relkind == RELKIND_MATVIEW)
! 		tdinfo->dobj.objType = DO_REFRESH_MATVIEW;
! 	else
! 		tdinfo->dobj.objType = DO_TABLE_DATA;
  
  	/*
  	 * Note: use tableoid 0 so that this object won't be mistaken for
***************
*** 3920,3925 **** getTables(Archive *fout, int *numTables)
--- 3966,3972 ----
  	int			i_toastoid;
  	int			i_toastfrozenxid;
  	int			i_relpersistence;
+ 	int			i_relisvalid;
  	int			i_owning_tab;
  	int			i_owning_col;
  	int			i_reltablespace;
***************
*** 3964,3970 **** 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, "
--- 4011,4017 ----
  						  "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, "
***************
*** 3978,3990 **** 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)
  	{
--- 4025,4037 ----
  						  "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)
  	{
***************
*** 4000,4006 **** 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, "
--- 4047,4053 ----
  						  "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, "
***************
*** 4035,4041 **** 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, "
--- 4082,4088 ----
  						  "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, "
***************
*** 4070,4076 **** 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, "
--- 4117,4123 ----
  						  "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, "
***************
*** 4106,4112 **** 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, "
--- 4153,4159 ----
  						  "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, "
***************
*** 4141,4147 **** 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, "
--- 4188,4194 ----
  						  "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, "
***************
*** 4172,4178 **** 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, "
--- 4219,4225 ----
  						  "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, "
***************
*** 4198,4204 **** 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, "
--- 4245,4251 ----
  						  "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, "
***************
*** 4234,4240 **** 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, "
--- 4281,4287 ----
  						  "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, "
***************
*** 4282,4287 **** getTables(Archive *fout, int *numTables)
--- 4329,4335 ----
  	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");
***************
*** 4323,4328 **** getTables(Archive *fout, int *numTables)
--- 4371,4377 ----
  		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));
***************
*** 4518,4525 **** 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 */
--- 4567,4577 ----
  	{
  		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 */
***************
*** 4733,4739 **** 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);
--- 4785,4794 ----
  		{
  			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);
***************
*** 5101,5112 **** 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,
--- 5156,5169 ----
  		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,
***************
*** 7312,7317 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7369,7380 ----
  		case DO_INDEX:
  			dumpIndex(fout, (IndxInfo *) dobj);
  			break;
+ 		case DO_REFRESH_MATVIEW:
+ 			refreshMatViewData(fout, (TableDataInfo *) dobj);
+ 			break;
+ 		case DO_MATVIEW_INDEX:
+ 			dumpMatViewIndex(fout, (IndxInfo *) dobj);
+ 			break;
  		case DO_RULE:
  			dumpRule(fout, (RuleInfo *) dobj);
  			break;
***************
*** 12351,12367 **** 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 */
--- 12414,12485 ----
  }
  
  /*
+  * 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 */
***************
*** 12382,12425 **** 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
--- 12500,12509 ----
  	/* 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
***************
*** 12436,12484 **** 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;
  
--- 12520,12579 ----
  		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;
  
***************
*** 12550,12558 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  				actual_atts++;
  
  				/* Attribute name */
! 				appendPQExpBuffer(q, "%s ",
  								  fmtId(tbinfo->attnames[j]));
  
  				if (tbinfo->attisdropped[j])
  				{
  					/*
--- 12645,12657 ----
  				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])
  				{
  					/*
***************
*** 12560,12566 **** 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;
  				}
--- 12659,12665 ----
  					 * 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;
  				}
***************
*** 12568,12584 **** 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]));
  				}
--- 12667,12683 ----
  				/* 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]));
  				}
***************
*** 12685,12691 **** 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
--- 12784,12803 ----
  		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
***************
*** 12941,12947 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
  		dumpTableConstraintComment(fout, constr);
  	}
  
- 	destroyPQExpBuffer(query);
  	destroyPQExpBuffer(q);
  	destroyPQExpBuffer(delq);
  	destroyPQExpBuffer(labelq);
--- 13053,13058 ----
***************
*** 13110,13115 **** dumpIndex(Archive *fout, IndxInfo *indxinfo)
--- 13221,13302 ----
  }
  
  /*
+  * 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
   */
***************
*** 14435,14440 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
--- 14622,14629 ----
  				addObjectDependency(postDataBound, dobj->dumpId);
  				break;
  			case DO_INDEX:
+ 			case DO_REFRESH_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_REFRESH_MATVIEW,
! 	DO_MATVIEW_INDEX
  } DumpableObjectType;
  
  typedef struct _dumpableObject
***************
*** 253,258 **** typedef struct _tableInfo
--- 255,261 ----
  	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_REFRESH_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_REFRESH_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_REFRESH_MATVIEW:
+ 			snprintf(buf, bufsize,
+ 					 "REFRESH 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
***************
*** 717,727 **** 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"));
  
--- 717,736 ----
  	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"));
  
***************
*** 738,744 **** 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
--- 747,753 ----
  
  	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
***************
*** 1315,1320 **** describeOneTableDetails(const char *schemaname,
--- 1324,1330 ----
  		 * 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)");
  	}
***************
*** 1343,1348 **** describeOneTableDetails(const char *schemaname,
--- 1353,1366 ----
  			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);
***************
*** 1385,1390 **** describeOneTableDetails(const char *schemaname,
--- 1403,1409 ----
  	cols = 2;
  
  	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ 		tableinfo.relkind == 'm' ||
  		tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
  	{
  		show_modifiers = true;
***************
*** 1404,1413 **** 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");
  	}
--- 1423,1434 ----
  	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");
  	}
***************
*** 1418,1425 **** 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;
  
--- 1439,1446 ----
  	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;
  
***************
*** 1507,1513 **** 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);
--- 1528,1535 ----
  							  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);
***************
*** 1515,1520 **** describeOneTableDetails(const char *schemaname,
--- 1537,1543 ----
  
  			/* 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);
***************
*** 1611,1654 **** 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 */
--- 1634,1639 ----
***************
*** 1687,1693 **** describeOneTableDetails(const char *schemaname,
  		 */
  		PQclear(result);
  	}
! 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  	{
  		/* Footer information about a table */
  		PGresult   *result = NULL;
--- 1672,1679 ----
  		 */
  		PQclear(result);
  	}
! 	else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 			 tableinfo.relkind == 'f')
  	{
  		/* Footer information about a table */
  		PGresult   *result = NULL;
***************
*** 1888,1894 **** describeOneTableDetails(const char *schemaname,
  		}
  
  		/* print rules */
! 		if (tableinfo.hasrules)
  		{
  			if (pset.sversion >= 80300)
  			{
--- 1874,1880 ----
  		}
  
  		/* print rules */
! 		if (tableinfo.hasrules && tableinfo.relkind != 'm')
  		{
  			if (pset.sversion >= 80300)
  			{
***************
*** 1983,1988 **** describeOneTableDetails(const char *schemaname,
--- 1969,2013 ----
  		}
  	}
  
+ 	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.
***************
*** 2106,2112 **** describeOneTableDetails(const char *schemaname,
  	/*
  	 * Finish printing the footer information about a table.
  	 */
! 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
  	{
  		PGresult   *result;
  		int			tuples;
--- 2131,2138 ----
  	/*
  	 * Finish printing the footer information about a table.
  	 */
! 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! 		tableinfo.relkind == 'f')
  	{
  		PGresult   *result;
  		int			tuples;
***************
*** 2303,2309 **** 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
--- 2329,2335 ----
  					  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
***************
*** 2585,2590 **** listDbRoleSettings(const char *pattern, const char *pattern2)
--- 2611,2617 ----
   * 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)
***************
*** 2596,2601 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2623,2629 ----
  	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;
  
***************
*** 2604,2611 **** 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);
  
--- 2632,2639 ----
  	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);
  
***************
*** 2616,2627 **** 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"),
--- 2644,2664 ----
  	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"),
***************
*** 2667,2672 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2704,2711 ----
  		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
***************
*** 220,225 **** slashUsage(unsigned short int pager)
--- 220,226 ----
  	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 ... */
***************
*** 844,850 **** psql_completion(char *text, int start, int end)
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
! 		"REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
  		"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
  		"TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
  		NULL
--- 875,881 ----
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
  		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
! 		"REASSIGN", "REFRESH", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
  		"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
  		"TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
  		NULL
***************
*** 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) &&
***************
*** 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'"
***************
*** 2740,2745 **** psql_completion(char *text, int start, int end)
--- 2814,2827 ----
  			 pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
  
+ /* REFRESH MATERIALIZED VIEW */
+ 	else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+ 		COMPLETE_WITH_CONST("MATERIALIZED VIEW");
+ 	else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
+ 			 pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "VIEW") == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ 
  /* REINDEX */
  	else if (pg_strcasecmp(prev_wd, "REINDEX") == 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/heap.h
--- b/src/include/catalog/heap.h
***************
*** 70,75 **** extern Oid heap_create_with_catalog(const char *relname,
--- 70,76 ----
  						 bool is_internal);
  
  extern void heap_create_init_fork(Relation rel);
+ extern bool heap_is_matview_init_fork(Relation rel);
  
  extern void heap_drop_with_catalog(Oid relid);
  
*** 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 ExecRefreshMatView(RefreshMatViewStmt *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 Oid 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 Oid 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_RefreshMatViewStmt,
  
  	/*
  	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 703,708 **** typedef struct RangeTblEntry
--- 703,709 ----
  	 */
  	Oid			relid;			/* OID of the relation */
  	char		relkind;		/* relation kind (see pg_class.relkind) */
+ 	bool		isResultRel;	/* used in target of SELECT INTO or similar */
  
  	/*
  	 * Fields valid for a subquery RTE (else NULL):
***************
*** 1125,1130 **** typedef enum ObjectType
--- 1126,1132 ----
  	OBJECT_INDEX,
  	OBJECT_LANGUAGE,
  	OBJECT_LARGEOBJECT,
+ 	OBJECT_MATVIEW,
  	OBJECT_OPCLASS,
  	OBJECT_OPERATOR,
  	OBJECT_OPFAMILY,
***************
*** 2436,2441 **** typedef struct ExplainStmt
--- 2438,2445 ----
   * 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
--- 2450,2470 ----
  	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;
  
  /* ----------------------
+  *		REFRESH MATERIALIZED VIEW Statement
+  * ----------------------
+  */
+ typedef struct RefreshMatViewStmt
+ {
+ 	NodeTag		type;
+ 	RangeVar   *relation;		/* relation to insert into */
+ } RefreshMatViewStmt;
+ 
+ /* ----------------------
   * 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 */
--- 2521,2527 ----
  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)
***************
*** 301,306 **** PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
--- 302,308 ----
  PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
  PG_KEYWORD("ref", REF, UNRESERVED_KEYWORD)
  PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
+ PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD)
  PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
  PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD)
*** a/src/include/storage/lwlock.h
--- b/src/include/storage/lwlock.h
***************
*** 79,84 **** typedef enum LWLockId
--- 79,85 ----
  	SerializablePredicateLockListLock,
  	OldSerXidLock,
  	SyncRepLock,
+ 	UnloggedMatViewInitLock,
  	/* Individual lock IDs end here */
  	FirstBufMappingLock,
  	FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
*** 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);
*** /dev/null
--- b/src/test/regress/expected/matview.out
***************
*** 0 ****
--- 1,176 ----
+ -- create a table to use as a basis for views and materialized views in various combinations
+ CREATE TABLE t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL);
+ INSERT INTO t VALUES
+   (1, 'x', 2),
+   (2, 'x', 3),
+   (3, 'y', 5),
+   (4, 'y', 7),
+   (5, 'z', 11);
+ -- we want a view based on the table, too, since views present additional challenges
+ CREATE VIEW tv AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type;
+ SELECT * FROM tv;
+  type | totamt 
+ ------+--------
+  y    |     12
+  z    |     11
+  x    |      5
+ (3 rows)
+ 
+ -- create a materialized view with no data, and confirm correct behavior
+ CREATE MATERIALIZED VIEW tm AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA;
+ SELECT * FROM tm;
+ ERROR:  materialized view "tm" has not been populated
+ HINT:  Use the REFRESH MATERIALIZED VIEW command.
+ REFRESH MATERIALIZED VIEW tm;
+ SELECT * FROM tm;
+  type | totamt 
+ ------+--------
+  y    |     12
+  z    |     11
+  x    |      5
+ (3 rows)
+ 
+ -- create various views
+ CREATE MATERIALIZED VIEW tvm AS SELECT * FROM tv;
+ SELECT * FROM tvm;
+  type | totamt 
+ ------+--------
+  y    |     12
+  z    |     11
+  x    |      5
+ (3 rows)
+ 
+ CREATE MATERIALIZED VIEW tmm AS SELECT sum(totamt) AS grandtot FROM tm;
+ CREATE MATERIALIZED VIEW tvmm AS SELECT sum(totamt) AS grandtot FROM tvm;
+ CREATE VIEW tvv AS SELECT sum(totamt) AS grandtot FROM tv;
+ CREATE MATERIALIZED VIEW tvvm AS SELECT * FROM tvv;
+ -- modify the underlying table data
+ INSERT INTO t VALUES (6, 'z', 13);
+ -- confirm pre- and post-refresh contents of fairly simple materialized views
+ SELECT * FROM tm ORDER BY type;
+  type | totamt 
+ ------+--------
+  x    |      5
+  y    |     12
+  z    |     11
+ (3 rows)
+ 
+ SELECT * FROM tvm ORDER BY type;
+  type | totamt 
+ ------+--------
+  x    |      5
+  y    |     12
+  z    |     11
+ (3 rows)
+ 
+ REFRESH MATERIALIZED VIEW tm;
+ REFRESH MATERIALIZED VIEW tvm;
+ SELECT * FROM tm ORDER BY type;
+  type | totamt 
+ ------+--------
+  x    |      5
+  y    |     12
+  z    |     24
+ (3 rows)
+ 
+ SELECT * FROM tvm ORDER BY type;
+  type | totamt 
+ ------+--------
+  x    |      5
+  y    |     12
+  z    |     24
+ (3 rows)
+ 
+ -- confirm pre- and post-refresh contents of nested materialized views
+ SELECT * FROM tmm;
+  grandtot 
+ ----------
+        28
+ (1 row)
+ 
+ SELECT * FROM tvmm;
+  grandtot 
+ ----------
+        28
+ (1 row)
+ 
+ SELECT * FROM tvvm;
+  grandtot 
+ ----------
+        28
+ (1 row)
+ 
+ REFRESH MATERIALIZED VIEW tmm;
+ REFRESH MATERIALIZED VIEW tvmm;
+ REFRESH MATERIALIZED VIEW tvvm;
+ SELECT * FROM tmm;
+  grandtot 
+ ----------
+        41
+ (1 row)
+ 
+ SELECT * FROM tvmm;
+  grandtot 
+ ----------
+        41
+ (1 row)
+ 
+ SELECT * FROM tvvm;
+  grandtot 
+ ----------
+        41
+ (1 row)
+ 
+ -- test diemv when the mv does not exist
+ DROP MATERIALIZED VIEW IF EXISTS tum;
+ NOTICE:  materialized view "tum" does not exist, skipping
+ -- make sure that an unlogged materialized view works (in the absence of a crash)
+ CREATE UNLOGGED MATERIALIZED VIEW tum AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA;
+ SELECT * FROM tum;
+ ERROR:  materialized view "tum" has not been populated
+ HINT:  Use the REFRESH MATERIALIZED VIEW command.
+ REFRESH MATERIALIZED VIEW tum;
+ SELECT * FROM tum;
+  type | totamt 
+ ------+--------
+  y    |     12
+  z    |     24
+  x    |      5
+ (3 rows)
+ 
+ TRUNCATE tum;
+ SELECT * FROM tum;
+ ERROR:  materialized view "tum" has not been populated
+ HINT:  Use the REFRESH MATERIALIZED VIEW command.
+ REFRESH MATERIALIZED VIEW tum;
+ SELECT * FROM tum;
+  type | totamt 
+ ------+--------
+  y    |     12
+  z    |     24
+  x    |      5
+ (3 rows)
+ 
+ -- test diemv when the mv does exist
+ DROP MATERIALIZED VIEW IF EXISTS tum;
+ -- make sure that dependencies are reported properly when they block the drop
+ DROP TABLE t;
+ ERROR:  cannot drop table t because other objects depend on it
+ DETAIL:  view tv depends on table t
+ view tvv depends on view tv
+ materialized view tm depends on table t
+ materialized view tmm depends on materialized view tm
+ materialized view tvm depends on table t
+ materialized view tvmm depends on materialized view tvm
+ materialized view tvvm depends on table t
+ HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ -- make sure dependencies are dropped and reported
+ DROP TABLE t CASCADE;
+ NOTICE:  drop cascades to 7 other objects
+ DETAIL:  drop cascades to view tv
+ drop cascades to view tvv
+ drop cascades to materialized view tm
+ drop cascades to materialized view tmm
+ drop cascades to materialized view tvm
+ drop cascades to materialized view tvmm
+ drop cascades to materialized view tvvm
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 88,94 **** test: privileges security_label collate
  # ----------
  # Another group of parallel tests
  # ----------
! test: misc alter_generic
  
  # rules cannot run concurrently with any test that creates a view
  test: rules
--- 88,94 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: misc alter_generic matview
  
  # rules cannot run concurrently with any test that creates a view
  test: rules
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 97,102 **** test: security_label
--- 97,103 ----
  test: collate
  test: misc
  test: alter_generic
+ test: matview
  test: rules
  test: event_trigger
  test: select_views
*** /dev/null
--- b/src/test/regress/sql/matview.sql
***************
*** 0 ****
--- 1,70 ----
+ -- create a table to use as a basis for views and materialized views in various combinations
+ CREATE TABLE t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL);
+ INSERT INTO t VALUES
+   (1, 'x', 2),
+   (2, 'x', 3),
+   (3, 'y', 5),
+   (4, 'y', 7),
+   (5, 'z', 11);
+ 
+ -- we want a view based on the table, too, since views present additional challenges
+ CREATE VIEW tv AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type;
+ SELECT * FROM tv;
+ 
+ -- create a materialized view with no data, and confirm correct behavior
+ CREATE MATERIALIZED VIEW tm AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA;
+ SELECT * FROM tm;
+ REFRESH MATERIALIZED VIEW tm;
+ SELECT * FROM tm;
+ 
+ -- create various views
+ CREATE MATERIALIZED VIEW tvm AS SELECT * FROM tv;
+ SELECT * FROM tvm;
+ CREATE MATERIALIZED VIEW tmm AS SELECT sum(totamt) AS grandtot FROM tm;
+ CREATE MATERIALIZED VIEW tvmm AS SELECT sum(totamt) AS grandtot FROM tvm;
+ CREATE VIEW tvv AS SELECT sum(totamt) AS grandtot FROM tv;
+ CREATE MATERIALIZED VIEW tvvm AS SELECT * FROM tvv;
+ 
+ -- modify the underlying table data
+ INSERT INTO t VALUES (6, 'z', 13);
+ 
+ -- confirm pre- and post-refresh contents of fairly simple materialized views
+ SELECT * FROM tm ORDER BY type;
+ SELECT * FROM tvm ORDER BY type;
+ REFRESH MATERIALIZED VIEW tm;
+ REFRESH MATERIALIZED VIEW tvm;
+ SELECT * FROM tm ORDER BY type;
+ SELECT * FROM tvm ORDER BY type;
+ 
+ -- confirm pre- and post-refresh contents of nested materialized views
+ SELECT * FROM tmm;
+ SELECT * FROM tvmm;
+ SELECT * FROM tvvm;
+ REFRESH MATERIALIZED VIEW tmm;
+ REFRESH MATERIALIZED VIEW tvmm;
+ REFRESH MATERIALIZED VIEW tvvm;
+ SELECT * FROM tmm;
+ SELECT * FROM tvmm;
+ SELECT * FROM tvvm;
+ 
+ -- test diemv when the mv does not exist
+ DROP MATERIALIZED VIEW IF EXISTS tum;
+ 
+ -- make sure that an unlogged materialized view works (in the absence of a crash)
+ CREATE UNLOGGED MATERIALIZED VIEW tum AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA;
+ SELECT * FROM tum;
+ REFRESH MATERIALIZED VIEW tum;
+ SELECT * FROM tum;
+ TRUNCATE tum;
+ SELECT * FROM tum;
+ REFRESH MATERIALIZED VIEW tum;
+ SELECT * FROM tum;
+ 
+ -- test diemv when the mv does exist
+ DROP MATERIALIZED VIEW IF EXISTS tum;
+ 
+ -- make sure that dependencies are reported properly when they block the drop
+ DROP TABLE t;
+ 
+ -- make sure dependencies are dropped and reported
+ DROP TABLE t CASCADE;
#2Kevin Grittner
kgrittn@mail.com
In reply to: Kevin Grittner (#1)
1 attachment(s)

[resending with patch compressed, since original post didn't make
it through to the list]

Here is a new version of the patch, with most issues discussed in
previous posts fixed.

I've been struggling with two areas:

- pg_dump sorting for MVs which depend on other MVs
- proper handling of the relisvalid flag for unlogged MVs after recovery

I've been hacking at the code in those areas without success;
what's here is the least broken form I have, but work is still
needed for these cases. Any other problems are news to me.

In addition, the docs need another pass, and there is an open
question about what is the right thing to use for TRUNCATE syntax.

-Kevin

Attachments:

matview-v2.patch.gzapplication/x-gzip; charset=utf-8; name=matview-v2.patch.gzDownload
#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#2)

"Kevin Grittner" <kgrittn@mail.com> writes:

I've been struggling with two areas:
- pg_dump sorting for MVs which depend on other MVs

Surely that should fall out automatically given that the dependency is
properly expressed in pg_depend?

If you mean you're trying to get it to cope with circular dependencies
between MVs, it might take some work on the pg_dump side, but plain
ordering shouldn't require new code.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Kevin Grittner
kgrittn@mail.com
In reply to: Tom Lane (#3)

Tom Lane wrote:

"Kevin Grittner" <kgrittn@mail.com> writes:

I've been struggling with two areas:
- pg_dump sorting for MVs which depend on other MVs

Surely that should fall out automatically given that the
dependency is properly expressed in pg_depend?

If you mean you're trying to get it to cope with circular
dependencies between MVs, it might take some work on the pg_dump
side, but plain ordering shouldn't require new code.

The *definitions* sort properly, but what I'm trying to do is
define them WITH NO DATA and load data after all the COPY
statements for tables. If mva is referenced by mvb, the goal is the
REFRESH mva, build its indexes before running REFRESH for mvb and
building its indexes. To do things in any other order does't seem
to me to leave things after restore in the same state they were in
at the time of the dump.

So I should have been a little more verbose describing the problem:
pg_dump sorting of REFRESH and CREATE INDEX steps for MVs which
depend on other MVs.

Last night I found why my previous attempts had been failing -- I
was trying to build the dependencies at the wrong point in the dump
process, after the sorts had already been done.  Now that I've
spotted that fundamental flaw, I think I can get this out of the
way without too much more fanfare. I kept thinking I had something
wrong in the detail of my approach, while the problem was at a much
higher level.

Where I really need someone to hit me upside the head with a
clue-stick is the code I added to the bottom of RelationBuildDesc()
in relcache.c. The idea is that on first access to an unlogged MV,
to detect that the heap has been replaced by the init fork, set
relisvalid to false, and make the heap look normal again. I
couldn't see any way to do that which wasn't a kludge, and I can't
figure out how to deal with relcache properly in implementing that
kludge. Either a tip about the right way to work the kludge, or a
suggestion for a less kludgy alternative would be welcome.

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Thom Brown
thom@linux.com
In reply to: Kevin Grittner (#1)

On 16 January 2013 05:40, Kevin Grittner <kgrittn@mail.com> wrote:

Here is a new version of the patch, with most issues discussed in
previous posts fixed.

I've been struggling with two areas:

- pg_dump sorting for MVs which depend on other MVs
- proper handling of the relisvalid flag for unlogged MVs after recovery

Some weirdness:

postgres=# CREATE VIEW v_test2 AS SELECT 1 moo;
CREATE VIEW
postgres=# CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM
v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2;
SELECT 2
postgres=# \d+ mv_test2
Materialized view "public.mv_test2"
Column | Type | Modifiers | Storage | Stats target | Description
----------+---------+-----------+---------+--------------+-------------
moo | integer | | plain | |
?column? | integer | | plain | |
View definition:
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?";
Has OIDs: no

The "weirdness" I refer you to is the view definition. This does not occur
with a straightforward UNION.

This does not occur with a regular view:

postgres=# CREATE VIEW v_test3 AS SELECT moo, 2*moo FROM v_test2 UNION ALL
SELECT moo, 3*moo FROM v_test2;
CREATE VIEW
postgres=# \d+ v_test3
View "public.v_test3"
Column | Type | Modifiers | Storage | Description
----------+---------+-----------+---------+-------------
moo | integer | | plain |
?column? | integer | | plain |
View definition:
SELECT v_test2.moo, 2 * v_test2.moo
FROM v_test2
UNION ALL
SELECT v_test2.moo, 3 * v_test2.moo
FROM v_test2;

--
Thom

#6Kevin Grittner
kgrittn@mail.com
In reply to: Thom Brown (#5)

Thom Brown wrote:

Some weirdness:

postgres=# CREATE VIEW v_test2 AS SELECT 1 moo;
CREATE VIEW
postgres=# CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM
v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2;
SELECT 2
postgres=# \d+ mv_test2
 Materialized view "public.mv_test2"
 Column | Type | Modifiers | Storage | Stats target | Description
----------+---------+-----------+---------+--------------+-------------
 moo | integer | | plain | |
 ?column? | integer | | plain | |
View definition:
 SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?";

You are very good at coming up with these, Thom!

Will investigate.

Can you confirm that *selecting* from the MV works as you would
expect; it is just the presentation in \d+ that's a problem?

Thanks,

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Thom Brown
thom@linux.com
In reply to: Kevin Grittner (#6)

On 16 January 2013 17:20, Kevin Grittner <kgrittn@mail.com> wrote:

Thom Brown wrote:

Some weirdness:

postgres=# CREATE VIEW v_test2 AS SELECT 1 moo;
CREATE VIEW
postgres=# CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM
v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2;
SELECT 2
postgres=# \d+ mv_test2
Materialized view "public.mv_test2"
Column | Type | Modifiers | Storage | Stats target | Description
----------+---------+-----------+---------+--------------+-------------
moo | integer | | plain | |
?column? | integer | | plain | |
View definition:
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?";

You are very good at coming up with these, Thom!

Will investigate.

Can you confirm that *selecting* from the MV works as you would
expect; it is just the presentation in \d+ that's a problem?

Yes, nothing wrong with using the MV, or refreshing it:

postgres=# TABLE mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# SELECT * FROM mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# REFRESH MATERIALIZED VIEW mv_test2;
REFRESH MATERIALIZED VIEW

But a pg_dump of the MV has the same issue as the view definition:

--
-- Name: mv_test2; Type: MATERIALIZED VIEW; Schema: public; Owner: thom;
Tablespace:
--

CREATE MATERIALIZED VIEW mv_test2 (
moo,
"?column?"
) AS
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?"
WITH NO DATA;

--
Thom

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#4)

"Kevin Grittner" <kgrittn@mail.com> writes:

Tom Lane wrote:

Surely that should fall out automatically given that the
dependency is properly expressed in pg_depend?

The *definitions* sort properly, but what I'm trying to do is
define them WITH NO DATA and load data after all the COPY
statements for tables. If mva is referenced by mvb, the goal is the
REFRESH mva, build its indexes before running REFRESH for mvb and
building its indexes. To do things in any other order does't seem
to me to leave things after restore in the same state they were in
at the time of the dump.

Ah. Can't you treat this using the same pg_dump infrastructure as
for the data for an ordinary table? The dependencies made for the
TableDataInfo object might be a bit different, but after that it
seems like the sort logic ought to be happy.

Where I really need someone to hit me upside the head with a
clue-stick is the code I added to the bottom of RelationBuildDesc()
in relcache.c. The idea is that on first access to an unlogged MV,
to detect that the heap has been replaced by the init fork, set
relisvalid to false, and make the heap look normal again.

Hmm. I agree that relcache.c has absolutely no business doing that,
but not sure what else to do instead. Seems like it might be better
done at first touch of the MV in the parser, rewriter, or planner ---
but the fact that I can't immediately decide which of those is right
makes me feel that it's still too squishy.

I'm also wondering about locking issues there. Obviously you don't
want more than one backend trying to rebuild the MV.

Do we really need unlogged MVs in the first iteration? Seems like
that's adding a whole bunch of new issues, when you have quite enough
already without that.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#8)

Do we really need unlogged MVs in the first iteration? Seems like
that's adding a whole bunch of new issues, when you have quite enough
already without that.

While I think there is strong user demand for unlogged MVs, if we can
get MVs without unlogged ones for 9.3, I say go for that. We'll add
unlogged in 9.4.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#8)

On Wed, Jan 16, 2013 at 1:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Where I really need someone to hit me upside the head with a
clue-stick is the code I added to the bottom of RelationBuildDesc()
in relcache.c. The idea is that on first access to an unlogged MV,
to detect that the heap has been replaced by the init fork, set
relisvalid to false, and make the heap look normal again.

Hmm. I agree that relcache.c has absolutely no business doing that,
but not sure what else to do instead. Seems like it might be better
done at first touch of the MV in the parser, rewriter, or planner ---
but the fact that I can't immediately decide which of those is right
makes me feel that it's still too squishy.

I think we shouldn't be doing that at all. The whole business of
transferring the relation-is-invalid information from the relation to
a pg_class flag seems like a Rube Goldberg device to me. I'm still
not convinced that we should have a relation-is-invalid flag at all,
but can we at least not have two?

It seems perfectly adequate to detect that the MV is invalid when we
actually try to execute a plan - that is, when we first access the
heap or one of its indexes. So the bit can just live in the
file-on-disk, and there's no need to have a second copy of it in
pg_class.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Thom Brown
thom@linux.com
In reply to: Thom Brown (#7)

On 16 January 2013 17:25, Thom Brown <thom@linux.com> wrote:

On 16 January 2013 17:20, Kevin Grittner <kgrittn@mail.com> wrote:

Thom Brown wrote:

Some weirdness:

postgres=# CREATE VIEW v_test2 AS SELECT 1 moo;
CREATE VIEW
postgres=# CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM
v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2;
SELECT 2
postgres=# \d+ mv_test2
Materialized view "public.mv_test2"
Column | Type | Modifiers | Storage | Stats target | Description
----------+---------+-----------+---------+--------------+-------------
moo | integer | | plain | |
?column? | integer | | plain | |
View definition:
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?";

You are very good at coming up with these, Thom!

Will investigate.

Can you confirm that *selecting* from the MV works as you would
expect; it is just the presentation in \d+ that's a problem?

Yes, nothing wrong with using the MV, or refreshing it:

postgres=# TABLE mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# SELECT * FROM mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# REFRESH MATERIALIZED VIEW mv_test2;
REFRESH MATERIALIZED VIEW

But a pg_dump of the MV has the same issue as the view definition:

--
-- Name: mv_test2; Type: MATERIALIZED VIEW; Schema: public; Owner: thom;
Tablespace:
--

CREATE MATERIALIZED VIEW mv_test2 (
moo,
"?column?"
) AS
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?"
WITH NO DATA;

A separate issue is with psql tab-completion:

postgres=# COMMENT ON MATERIALIZED VIEW ^IIS

This should be offering MV names instead of prematurely providing the "IS"
keyword.

--
Thom

#12Simon Riggs
simon@2ndquadrant.com
In reply to: Kevin Grittner (#1)

On 16 January 2013 05:40, Kevin Grittner <kgrittn@mail.com> wrote:

Here is a new version of the patch, with most issues discussed in
previous posts fixed.

Looks good.

The patch implements one kind of MV. In the future, we hope to have
other features or other kinds of MV alongside this:
* Snapshot MV - built once at start and then refreshed by explicit command only
* Snapshot MV with fast refresh
* Maintained MV (lazy) - changes trickle continuously into lazy MVs
* Maintained MV (transactional) - changes applied as part of write transactions
and or others

So I think we should agree now some aspects of those other options so
we can decide syntax. Otherwise we'll be left in the situation that
what we implement in 9.3 becomes the default for all time and/or we
have difficulties adding things later. e.g.
REFRESH ON COMMAND

Also, since there is no optimizer linkage between these MVs and the
tables they cover, I think we need to have that explicitly as a
command option, e.g.
DISABLE OPTIMIZATION

That way in the future we can implement "ENABLE OPTIMIZATION" mode and
REFRESH TRANSACTIONAL mode as separate items.

So all I am requesting is that we add additional syntax now, so that
future additional features are clear.

Please suggest syntax, not wedded to those... and we may want to use
more compatible syntax also.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Thom Brown
thom@linux.com
In reply to: Thom Brown (#11)

On 17 January 2013 16:03, Thom Brown <thom@linux.com> wrote:

On 16 January 2013 17:25, Thom Brown <thom@linux.com> wrote:

On 16 January 2013 17:20, Kevin Grittner <kgrittn@mail.com> wrote:

Thom Brown wrote:

Some weirdness:

postgres=# CREATE VIEW v_test2 AS SELECT 1 moo;
CREATE VIEW
postgres=# CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM
v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2;
SELECT 2
postgres=# \d+ mv_test2
Materialized view "public.mv_test2"
Column | Type | Modifiers | Storage | Stats target | Description
----------+---------+-----------+---------+--------------+-------------
moo | integer | | plain | |
?column? | integer | | plain | |
View definition:
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?";

You are very good at coming up with these, Thom!

Will investigate.

Can you confirm that *selecting* from the MV works as you would
expect; it is just the presentation in \d+ that's a problem?

Yes, nothing wrong with using the MV, or refreshing it:

postgres=# TABLE mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# SELECT * FROM mv_test2;
moo | ?column?
-----+----------
1 | 2
1 | 3
(2 rows)

postgres=# REFRESH MATERIALIZED VIEW mv_test2;
REFRESH MATERIALIZED VIEW

But a pg_dump of the MV has the same issue as the view definition:

--
-- Name: mv_test2; Type: MATERIALIZED VIEW; Schema: public; Owner: thom;
Tablespace:
--

CREATE MATERIALIZED VIEW mv_test2 (
moo,
"?column?"
) AS
SELECT "*SELECT* 1".moo, "*SELECT* 1"."?column?"
WITH NO DATA;

A separate issue is with psql tab-completion:

postgres=# COMMENT ON MATERIALIZED VIEW ^IIS

This should be offering MV names instead of prematurely providing the "IS"
keyword.

Also in doc/src/sgml/ref/alter_materialized_view.sgml:

s/materailized/materialized/

In src/backend/executor/execMain.c:

s/referrenced/referenced/

--
Thom

#14Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#10)

On Thu, Jan 17, 2013 at 07:54:55AM -0500, Robert Haas wrote:

On Wed, Jan 16, 2013 at 1:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Where I really need someone to hit me upside the head with a
clue-stick is the code I added to the bottom of RelationBuildDesc()
in relcache.c. The idea is that on first access to an unlogged MV,
to detect that the heap has been replaced by the init fork, set
relisvalid to false, and make the heap look normal again.

Hmm. I agree that relcache.c has absolutely no business doing that,
but not sure what else to do instead. Seems like it might be better
done at first touch of the MV in the parser, rewriter, or planner ---
but the fact that I can't immediately decide which of those is right
makes me feel that it's still too squishy.

I think we shouldn't be doing that at all. The whole business of
transferring the relation-is-invalid information from the relation to
a pg_class flag seems like a Rube Goldberg device to me. I'm still
not convinced that we should have a relation-is-invalid flag at all,
but can we at least not have two?

It seems perfectly adequate to detect that the MV is invalid when we
actually try to execute a plan - that is, when we first access the
heap or one of its indexes. So the bit can just live in the
file-on-disk, and there's no need to have a second copy of it in
pg_class.

Like Kevin, I want a way to distinguish unpopulated MVs from MVs that
genuinely yielded the empty set at last refresh. I agree that there's no
particular need to store that fact in pg_class, and I would much prefer only
storing it one way in any case. A user-visible disadvantage of the current
implementation is that relisvalid remains stale until something opens the rel.
That's fine for the system itself, but it can deceive user-initiated catalog
queries. Imagine a check_postgres action that looks for invalid MVs to
complain about. It couldn't just scan pg_class; it would need to first do
something that opens every MV.

I suggest the following:

1. Let an invalid MV have a zero-length heap. Distinguish a valid, empty MV
by giving it a page with no tuples. This entails VACUUM[1]For the time being, it's unfortunate to VACUUM materialized views at all; they only ever bear frozen tuples. not truncating
MVs below one page and the refresh operation, where necessary, extending
the relation from zero pages to one.
2. Remove pg_class.relisvalid.
3. Add a bool field to RelationData. The word "valid" is used in that context
to refer to the validity of the structure itself, so perhaps call the new
field rd_scannable. RelationIsFlaggedAsValid() can become a macro;
consider changing its name for consistency with the field name.
4. During relcache build, set the field to "RelationGetNumberBlocks(rel) != 0"
for MVs, fixed "true" for everyone else. Any operation that changes the
field must, and probably would anyway, instigate a relcache invalidation.
5. Expose a database function, say pg_relation_scannable(), for querying the
current state of a relation. This supports user-level monitoring.

Does that seem reasonable? One semantic difference to keep in mind is that
unlogged MVs will be considered invalid on the standby while valid on the
master. That's essentially an accurate report, so I won't mind it.

For the benefit of the archives, I note that we almost need not truncate an
unlogged materialized view during crash recovery. MVs are refreshed in a
VACUUM FULL-like manner: fill a new relfilenode, fsync it, and point the MV's
pg_class to that relfilenode. When a crash occurs with no refresh in flight,
the latest refresh had been safely synced. When a crash cuts short a refresh,
the pg_class update will not stick, and the durability of the old heap is not
in doubt. However, non-btree index builds don't have the same property; we
would need to force an immediate sync of the indexes to be safe here. It
would remain necessary to truncate unlogged MVs when recovering a base backup,
which may contain a partially-written refresh that did eventually commit.
Future MV variants that modify the MV in place would also need the usual
truncate on crash.

I'm going to follow this with a review covering a broader range of topics.

Thanks,
nm

[1]: For the time being, it's unfortunate to VACUUM materialized views at all; they only ever bear frozen tuples.
they only ever bear frozen tuples.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#1)

Hi Kevin,

The patch conflicts with git master; I tested against master@{2013-01-20}.

On Wed, Jan 16, 2013 at 12:40:55AM -0500, Kevin Grittner wrote:

I've been struggling with two areas:

- pg_dump sorting for MVs which depend on other MVs

From your later messages, I understand that you have a way forward on this.

- proper handling of the relisvalid flag for unlogged MVs after recovery

I have discussed this in a separate email. While reading the patch to assess
that topic, I found a few more things:

*** 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 "

PostgreSQL 8.3 clusters won't contain materialized views, so it doesn't really
matter whether this change happens or not. I suggest adding a comment,
whether or not you keep the code change.

*** 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.

This smells like a bypass of mandatory access control. Unless you've
determined that this is correct within the sepgsql security model, I suggest
starting with a draconian policy, like simply crippling MVs. Even if you have
determined that, separating out the nontrivial sepgsql support might be good.
The set of ideal reviewers is quite different.

*/
#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')");

It concerns me slightly that older vacuumlo could silently remove large
objects still referenced by MVs. Only slightly, though, because the next MV
refresh would remove those references anyway. Is there anything we should do
to help that situation? If nothing else, perhaps backpatch this patch hunk.

+    <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>

Let's not support OIDs on MVs. They'll be regenerated on every refresh.

***************
*** 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,

If I'm reading this right, you always overwrite the passed-in dest without
looking at it. What's the intent here?

+ 	/*
+ 	 * Kludge here to allow refresh of a materialized view which is invalid
+ 	 * (that is, it was created WITH NO DATA or was TRUNCATED). We flag the
+ 	 * first two RangeTblEntry list elements, which were added to the front
+ 	 * of the rewritten Query to keep the rules system happy, with the
+ 	 * isResultRel flag to indicate that it is OK if they are flagged as
+ 	 * invalid.
+ 	 */
+ 	rtable = dataQuery->rtable;
+ 	((RangeTblEntry *) linitial(rtable))->isResultRel = true;
+ 	((RangeTblEntry *) lsecond(rtable))->isResultRel = true;

Is it safe to assume that the first two RTEs are the correct ones to flag?

+ 	/*
+ 	 * 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);

The check_constraints argument should be "true", because the refresh could
have invalidated a UNIQUE index.

***************
*** 3049,3055 **** 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;
--- 3104,3110 ----
break;
case AT_ClusterOn:		/* CLUSTER ON */
case AT_DropCluster:	/* SET WITHOUT CLUSTER */
! 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);

If the user desires an actually-clustered MV, he must re-CLUSTER it after each
refresh. That deserves a documentation mention.

***************
*** 724,729 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 765,775 ----
ExecCheckRTPerms(rangeTable, true);

/*
+ * Ensure that all referrenced relations are flagged as valid.

Typo.

+ */
+ ExecCheckRelationsValid(rangeTable);

I believe this ought to happen after the executor lock acquisitions, perhaps
right in ExecOpenScanRelation(). Since you'll then have an open Relation,
RelationIsFlaggedAsValid() can use the relcache.

***************
*** 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;
+ 		}

Would you elaborate on this?

+ 	/* Strip off the trailing semicolon so that other things may follow. */
+ 	appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);

I suggest verifying that the last character is indeed a semicolon.

/*
+  * dumpMatViewIndex
+  *	  write out to fout a user-defined index
+  */
+ static void
+ dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo)

This is so similar to dumpIndex(); can we avoid this level of duplication?

*** /dev/null
--- b/src/test/regress/sql/matview.sql
+ -- test diemv when the mv does exist
+ DROP MATERIALIZED VIEW IF EXISTS tum;
+ 
+ -- make sure that dependencies are reported properly when they block the drop
+ DROP TABLE t;
+ 
+ -- make sure dependencies are dropped and reported
+ DROP TABLE t CASCADE;

Please retain an interesting sample of materialized views in the regression
database. Among other benefits, the pg_upgrade test suite exercises pg_dump
and pg_upgrade for all object types retained in the regression database.

The regression tests should probably include a few other wrinkles, like an
index on a MV.

Creating a RULE on an MV succeeds, but refreshing the view then fails:

[local] test=# create rule mvrule as on insert to mymv where 1 = 0 do also select 1;
CREATE RULE
[local] test=# REFRESH MATERIALIZED VIEW mymv;
ERROR: materialized view "mymv" has too many rules

The documentation is a good start. I would expect a brief introduction in
Tutorial -> Advanced Features and possibly a deeper discussion under The SQL
Language. I suggest updating Explicit Locking to mention the new commands;
users will be interested in the lock level of a refresh.

You have chosen to make pg_dump preserve the valid-or-invalid state of each
MV. That seems reasonable, though I'm slightly concerned about the case of a
dump taken from a standby.

We support ALTER TABLE against regular views for historical reasons. When we
added foreign tables, we did not extend that permissiveness; one can only use
ALTER FOREIGN TABLE on foreign tables. Please do the same for materialized
views. See RangeVarCallbackForAlterRelation(). Note that "ALTER TABLE
... RENAME colname TO newname" and "ALTER TABLE ... RENAME CONSTRAINT" are
currently supported for MVs by ALTER TABLE but not by ALTER MATERIALIZED VIEW.

There's no documented support for table constraints on MVs, but UNIQUE
constraints are permitted:

[local] test=# alter materialized view mymv add unique (c);
ALTER MATERIALIZED VIEW
[local] test=# alter materialized view mymv add check (c > 0);
ERROR: "mymv" is not a table
[local] test=# alter materialized view mymv add primary key (c);
ERROR: "mymv" is not a table or foreign table

Some of the ALTER TABLE variants would make plenty of sense for MVs:

ALTER [ COLUMN ] column_name SET STATISTICS integer
ALTER [ COLUMN ] column_name SET ( attribute_option = value [, ... ] )
ALTER [ COLUMN ] column_name RESET ( attribute_option [, ... ] )
ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }

It wouldn't be a problem to skip those for the first patch, though.
Conversely, this syntax is accepted:

ALTER MATERIALIZED VIEW [ IF EXISTS ] name SET ( view_option_name [= view_option_value] [, ... ] )

But there are no available options. The only option accepted for regular
views, security_barrier, is rejected. MVs always have security_barrier
semantics, in any event.

Overall, I recommend auditing all the ALTER TABLE and ALTER VIEW options to
determine which ones make sense for MVs. For each one in the sensible set,
either allow it or add a comment indicating that it could reasonably be
allowed in the future. For each one outside the set, forbid it. Verify that
the documentation, the results of your evaluation, and the actual allowed
operations are all consistent.

Thanks,
nm

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Kevin Grittner
kgrittn@mail.com
In reply to: Noah Misch (#15)

Thanks for looking at this!

Noah Misch wrote:

For the benefit of the archives, I note that we almost need not truncate an
unlogged materialized view during crash recovery. MVs are refreshed in a
VACUUM FULL-like manner: fill a new relfilenode, fsync it, and point the MV's
pg_class to that relfilenode. When a crash occurs with no refresh in flight,
the latest refresh had been safely synced. When a crash cuts short a refresh,
the pg_class update will not stick, and the durability of the old heap is not
in doubt. However, non-btree index builds don't have the same property; we
would need to force an immediate sync of the indexes to be safe here. It
would remain necessary to truncate unlogged MVs when recovering a base backup,
which may contain a partially-written refresh that did eventually commit.
Future MV variants that modify the MV in place would also need the usual
truncate on crash.

Hmm. That's a very good observation. Perhaps the issue can be
punted to a future release where we start adding more incremental
updates to them. I'll think on that, but on the face of it, it
sounds like the best choice.

I'm going to follow this with a review covering a broader range
of topics.

I'll need time to digest the rest of it. As you note, recent
commits conflict with the last patch. Please look at the github
repo where I've been working on this. I'll post an updated patch
later today.

https://github.com/kgrittn/postgres/tree/matview

You might want to ignore the interim work on detecting the new
pg_dump dependencies through walking the internal structures. I
decided that was heading in a direction which might be
unnecessarily fragile and slow; so I tried writing it as a query
against the system tables. I'm pretty happy with the results.
Here's the query:

with recursive w as
(
select
   d1.objid,
   d1.objid as wrkid,
   d2.refobjid,
   c2.relkind as refrelkind
 from pg_depend d1
 join pg_class c1 on c1.oid = d1.objid
                 and c1.relkind = 'm'
                 and c1.relisvalid
 join pg_rewrite r1 on r1.ev_class = d1.objid
 join pg_depend d2 on d2.classid = 'pg_rewrite'::regclass
                  and d2.objid = r1.oid
                  and d2.refobjid <> d1.objid
 join pg_class c2 on c2.oid = d2.refobjid
                 and c2.relkind in ('m','v')
                 and c2.relisvalid
 where d1.classid = 'pg_class'::regclass
union
select
   w.objid,
   w.refobjid as wrkid,
   d3.refobjid,
   c3.relkind as refrelkind
 from w
 join pg_rewrite r3 on r3.ev_class = w.refobjid
 join pg_depend d3 on d3.classid = 'pg_rewrite'::regclass
                  and d3.objid = r3.oid
                  and d3.refobjid <> w.refobjid
 join pg_class c3 on c3.oid = d3.refobjid
                 and c3.relkind in ('m','v')
                 and c3.relisvalid
 where w.refrelkind <> 'm'
),
x as
(
select objid::regclass, refobjid::regclass from w
 where refrelkind = 'm'
)
select 'm'::text as type, x.objid, x.refobjid from x
union all
select
   'i'::text as type,
   x.objid,
   i.indexrelid as refobjid
 from x
 join pg_index i on i.indrelid = x.refobjid
                and i.indisvalid
;

If we bail on having pg_class.relisvalid, then it will obviously
need adjustment.

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#16)

On Thu, Jan 24, 2013 at 01:29:10PM -0500, Kevin Grittner wrote:

Noah Misch wrote:

For the benefit of the archives, I note that we almost need not truncate an
unlogged materialized view during crash recovery. MVs are refreshed in a
VACUUM FULL-like manner: fill a new relfilenode, fsync it, and point the MV's
pg_class to that relfilenode. When a crash occurs with no refresh in flight,
the latest refresh had been safely synced. When a crash cuts short a refresh,
the pg_class update will not stick, and the durability of the old heap is not
in doubt. However, non-btree index builds don't have the same property; we
would need to force an immediate sync of the indexes to be safe here. It
would remain necessary to truncate unlogged MVs when recovering a base backup,
which may contain a partially-written refresh that did eventually commit.
Future MV variants that modify the MV in place would also need the usual
truncate on crash.

Hmm. That's a very good observation. Perhaps the issue can be
punted to a future release where we start adding more incremental
updates to them. I'll think on that, but on the face of it, it
sounds like the best choice.

That situation is challenging for the same reason pg_class.relisvalid was hard
to implement for unlogged relations. The startup process doesn't know the
relkind of the unlogged-relation relfilenodes it cleans. If you can work
through all that, it's certainly a nice endpoint to not lose unlogged snapshot
MVs on crash. But I intended the first half of my message as the
recommendation and the above as a wish for the future.

You might want to ignore the interim work on detecting the new
pg_dump dependencies through walking the internal structures. I
decided that was heading in a direction which might be
unnecessarily fragile and slow; so I tried writing it as a query
against the system tables. I'm pretty happy with the results.
Here's the query:

with recursive w as

[snip]

Why is the dependency problem of ordering MV refreshes and MV index builds so
different from existing pg_dump dependency problems?

If we bail on having pg_class.relisvalid, then it will obviously
need adjustment.

Even if we don't have the column, we can have the fact of an MV's validity
SQL-visible in some other way.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Kevin Grittner
kgrittn@mail.com
In reply to: Noah Misch (#17)

Noah Misch wrote:

On Thu, Jan 24, 2013 at 01:29:10PM -0500, Kevin Grittner wrote:

Noah Misch wrote:

For the benefit of the archives, I note that we almost need not truncate an
unlogged materialized view during crash recovery. MVs are refreshed in a
VACUUM FULL-like manner: fill a new relfilenode, fsync it, and point the MV's
pg_class to that relfilenode. When a crash occurs with no refresh in flight,
the latest refresh had been safely synced. When a crash cuts short a refresh,
the pg_class update will not stick, and the durability of the old heap is not
in doubt. However, non-btree index builds don't have the same property; we
would need to force an immediate sync of the indexes to be safe here. It
would remain necessary to truncate unlogged MVs when recovering a base backup,
which may contain a partially-written refresh that did eventually commit.
Future MV variants that modify the MV in place would also need the usual
truncate on crash.

Hmm. That's a very good observation. Perhaps the issue can be
punted to a future release where we start adding more incremental
updates to them. I'll think on that, but on the face of it, it
sounds like the best choice.

That situation is challenging for the same reason pg_class.relisvalid was hard
to implement for unlogged relations. The startup process doesn't know the
relkind of the unlogged-relation relfilenodes it cleans. If you can work
through all that, it's certainly a nice endpoint to not lose unlogged snapshot
MVs on crash. But I intended the first half of my message as the
recommendation and the above as a wish for the future.

Well, if I just don't create an init fork for MVs, they are left as
they were on recovery, aren't they? So for 9.3, that solves that
issue, I think. pg_class.relisvald is a separate issue.

You might want to ignore the interim work on detecting the new
pg_dump dependencies through walking the internal structures. I
decided that was heading in a direction which might be
unnecessarily fragile and slow; so I tried writing it as a query
against the system tables. I'm pretty happy with the results.
Here's the query:

with recursive w as

[snip]

Why is the dependency problem of ordering MV refreshes and MV index builds so
different from existing pg_dump dependency problems?

If mva has indexes and is referenced by mvb, the CREATE statements
are all properly ordered, but you want mva populated and indexed
before you attempt to populate mvb. (Populated to get correct
results, indexed to get them quickly.) We don't have anything else
like that.

If we bail on having pg_class.relisvalid, then it will obviously
need adjustment.

Even if we don't have the column, we can have the fact of an MV's validity
SQL-visible in some other way.

Sure, I didn't say we had to abandon the query -- probably just
replace the relisvalid tests with a function call using the oid of
the MV.

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#18)

On Thu, Jan 24, 2013 at 03:14:15PM -0500, Kevin Grittner wrote:

Noah Misch wrote:

On Thu, Jan 24, 2013 at 01:29:10PM -0500, Kevin Grittner wrote:

Noah Misch wrote:

For the benefit of the archives, I note that we almost need not truncate an
unlogged materialized view during crash recovery. MVs are refreshed in a
VACUUM FULL-like manner: fill a new relfilenode, fsync it, and point the MV's
pg_class to that relfilenode. When a crash occurs with no refresh in flight,
the latest refresh had been safely synced. When a crash cuts short a refresh,
the pg_class update will not stick, and the durability of the old heap is not
in doubt. However, non-btree index builds don't have the same property; we
would need to force an immediate sync of the indexes to be safe here. It
would remain necessary to truncate unlogged MVs when recovering a base backup,
which may contain a partially-written refresh that did eventually commit.
Future MV variants that modify the MV in place would also need the usual
truncate on crash.

Hmm. That's a very good observation. Perhaps the issue can be
punted to a future release where we start adding more incremental
updates to them. I'll think on that, but on the face of it, it
sounds like the best choice.

That situation is challenging for the same reason pg_class.relisvalid was hard
to implement for unlogged relations. The startup process doesn't know the
relkind of the unlogged-relation relfilenodes it cleans. If you can work
through all that, it's certainly a nice endpoint to not lose unlogged snapshot
MVs on crash. But I intended the first half of my message as the
recommendation and the above as a wish for the future.

Well, if I just don't create an init fork for MVs, they are left as
they were on recovery, aren't they? So for 9.3, that solves that
issue, I think. pg_class.relisvald is a separate issue.

The startup process just looks for init forks, yes. But it's acceptable to
leave the unlogged MV materials alone during *crash* recovery only. When
recovering from a base backup, we once again need an init fork to refresh the
unlogged-MV relations. In turn, we would still need a relisvalid
implementation that copes. This is all solvable, sure, but it looks like a
trip off into the weeds relative to the core aim of this patch.

Why is the dependency problem of ordering MV refreshes and MV index builds so
different from existing pg_dump dependency problems?

If mva has indexes and is referenced by mvb, the CREATE statements
are all properly ordered, but you want mva populated and indexed
before you attempt to populate mvb. (Populated to get correct
results, indexed to get them quickly.) We don't have anything else
like that.

Is the REFRESH order just a replay of the CREATE order (with index builds
interspersed), or can it differ?

nm

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Kevin Grittner
kgrittn@mail.com
In reply to: Noah Misch (#19)
1 attachment(s)

Noah Misch wrote:

The patch conflicts with git master; I tested against master@{2013-01-20}.

New patch rebased, fixes issues raised by Thom Brown, and addresses
some of your points.

PostgreSQL 8.3 clusters won't contain materialized views, so it doesn't really
matter whether this change happens or not. I suggest adding a comment,
whether or not you keep the code change.

Reverted code changes to version_old_8_3.c; added comments.

*** 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.

This smells like a bypass of mandatory access control. Unless you've
determined that this is correct within the sepgsql security model, I suggest
starting with a draconian policy, like simply crippling MVs. Even if you have
determined that, separating out the nontrivial sepgsql support might be good.
The set of ideal reviewers is quite different.

Robert suggested this way of coping for now. Will post just the
sepgsql separately to try to attract the right crowd to confirm.

It concerns me slightly that older vacuumlo could silently remove large
objects still referenced by MVs. Only slightly, though, because the next MV
refresh would remove those references anyway. Is there anything we should do
to help that situation? If nothing else, perhaps backpatch this patch hunk.

Defensive backpatching of this code sounds like a good idea to me.
I'm open to other opinions on whether we need to defend 9.3 and
later against earler versions of vacuumlo being run against them.

Let's not support OIDs on MVs. They'll be regenerated on every refresh.

Do they have any value for people who might want to use cursors? If
nobody speaks up for them, I will drop OID support for materialized
views.

If I'm reading this right, you always overwrite the passed-in dest without
looking at it. What's the intent here?

Let me get back to you on that one.

+ /*
+ * Kludge here to allow refresh of a materialized view which is invalid
+ * (that is, it was created WITH NO DATA or was TRUNCATED). We flag the
+ * first two RangeTblEntry list elements, which were added to the front
+ * of the rewritten Query to keep the rules system happy, with the
+ * isResultRel flag to indicate that it is OK if they are flagged as
+ * invalid.
+ */
+ rtable = dataQuery->rtable;
+ ((RangeTblEntry *) linitial(rtable))->isResultRel = true;
+ ((RangeTblEntry *) lsecond(rtable))->isResultRel = true;

Is it safe to assume that the first two RTEs are the correct ones to flag?

I'm trying to play along with UpdateRangeTableOfViewParse() in
view.c. See the comment in front of that function for details.

+ finish_heap_swap(matviewOid, OIDNewHeap, false, false, false, RecentXmin);

The check_constraints argument should be "true", because the refresh could
have invalidated a UNIQUE index.

Fixed.

If the user desires an actually-clustered MV, he must re-CLUSTER it after each
refresh. That deserves a documentation mention.

That point had not occurred to me. Let me see if I can fix that before changing docs.

+ * Ensure that all referrenced relations are flagged as valid.

Typo.

Fixed.

+ ExecCheckRelationsValid(rangeTable);

I believe this ought to happen after the executor lock acquisitions, perhaps
right in ExecOpenScanRelation(). Since you'll then have an open Relation,
RelationIsFlaggedAsValid() can use the relcache.

That would break MVs entirely. This probably deserves more
comments. It's a little fragile, but was the best way I found to
handle things. An MV has a rule associated with it, just like a
"regular" view, which is parse-analyzed but not rewritten or
planned. We need to allow the rewrite and planning for statements
which populate the view, but suppress expansion of the rule for
simple references. It is OK for an MV to be invalid if it is being
populated, but not if it is being referenced. Long story short,
this call helps determine which relations will be opened.

If someone can suggest a better alternative, I'll see what I can
do; otherwise I guess I should add comments around the key places.

***************
*** 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;
+ }

Would you elaborate on this?

It's diretly related to the point immediately preceding. At this
point we have thrown an error if the MV is invalid and being used
as a source of data. If it is the target of data it is flagged as
invalid so that it will not be expanded. Maybe we need a better way
to determine this, but I'm not sure just what to use.

+ /* Strip off the trailing semicolon so that other things may follow. */
+ appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);

I suggest verifying that the last character is indeed a semicolon.

How about if I have it exit_horribly if the semicolon added 21
lines up has disappeared? Or use Assert if we have that for the
frontend now?

+ static void
+ dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo)

This is so similar to dumpIndex(); can we avoid this level of duplication?

It is identical except for name. I can only assume that I thought I
needed a modified version and changed my mind. Removed.

Please retain an interesting sample of materialized views in the regression
database. Among other benefits, the pg_upgrade test suite exercises pg_dump
and pg_upgrade for all object types retained in the regression database.

OK

The regression tests should probably include a few other wrinkles, like an
index on a MV.

Yeah. Will do.

Creating a RULE on an MV succeeds, but refreshing the view then fails:

Fixed by prohibiting CREATE RULE on an MV.

The documentation is a good start. I would expect a brief introduction in
Tutorial -> Advanced Features and possibly a deeper discussion under The SQL
Language. I suggest updating Explicit Locking to mention the new commands;
users will be interested in the lock level of a refresh.

Yeah, the docs need another pass. It seemed prudent to make sure of
what I was documenting first.

You have chosen to make pg_dump preserve the valid-or-invalid state of each
MV. That seems reasonable, though I'm slightly concerned about the case of a
dump taken from a standby.

I'm not clear on the problem. Could you explain?

Some of the ALTER TABLE variants would make plenty of sense for MVs:

 ALTER [ COLUMN ] column_name SET STATISTICS integer
 ALTER [ COLUMN ] column_name SET ( attribute_option = value [, ... ] )
 ALTER [ COLUMN ] column_name RESET ( attribute_option [, ... ] )
 ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }

It wouldn't be a problem to skip those for the first patch, though.
Conversely, this syntax is accepted:

 ALTER MATERIALIZED VIEW [ IF EXISTS ] name SET ( view_option_name [= view_option_value] [, ... ] )

But there are no available options. The only option accepted for regular
views, security_barrier, is rejected. MVs always have security_barrier
semantics, in any event.

I think those are doc problems, not implementation of the
functionality. Will double-check and fix where needed.

Overall, I recommend auditing all the ALTER TABLE and ALTER VIEW options to
determine which ones make sense for MVs. For each one in the sensible set,
either allow it or add a comment indicating that it could reasonably be
allowed in the future. For each one outside the set, forbid it. Verify that
the documentation, the results of your evaluation, and the actual allowed
operations are all consistent.

I have already tried to do that in the coding, although maybe you
think more comments are needed there? The docs definitely need to
catch up. This part isn't in flux, so I'll fix that part of the
docs in the next day or two.

Thanks for the review!

-Kevin

Attachments:

matview-v3.patch.gzapplication/x-gzip; charset=utf-8; name=matview-v3.patch.gzDownload
�Qmatview-v3.patch�<�W���?��b�;-�l�U���P��Ml���6�G���Yr���=�o3��/K��@����`iwF��=;+oll0�fzn����g[u����C��fss�������5k4���-�7����'n�p25'0F��3���
��1��0o��+�~��0���[]9�_�?���^M#0��
�15L�\��0�
��W����I�V	��<|�3�XU�Fp@��g���� �\Y��Y|r����BNz�������v-DT���o��b��:�)2����@-����S�W[���ut��� w��I>o�%8�����ao����h;{�sF ���7��������I*�{��p�i������5)�4���2�UN?����Gc����_C��H���	���a�����\p��Ye}������U^�`T
q���8����0Iy�����a�p�zD�IOo
t6�/.��V��~8��A��U�7�	��g}�\?�B ��u��u/��o:��\�d	������i�+������o�m0�	nq6���A���7��-���I���ZDc��[u�������������Z����`�g� ;�I)��?�'_-�K�-����2�
�a}W�i4T8���o��d[�r�b#�/��D�
��0�oB������+#���r���S���"��fg���
#~$l��pM������pc��������svk�a89�_�1��W����%�s���&Ub�r����)xq�S�����m�7�\�'��b^l8�461`E����{nm~�}.�&���R�����Tk��6�w~j��o7w��U���C����c��#�i
����u�@�I����W�M-#H����l�L��r�(f���R�9If������E.��W��z�����h�(��o�GJ��%�����Qf�4��"U����Y��@����{�)���P�������|>
m��[n('���#�s�����Rb�zSk4�����K�	k>��������_�/��s3bC�O&9��=e��[N��r�D����`^8ux�s��-�W��o�j�����t�
��+=��/��]�N�!��2��<���c-wv`�P&�y�[C�W����L,�z��O������n�{af��5�����>DQ�����^0���\��5�2}7Z&�
�L-�p������N��89.g�
C����}�+9+�&b�p����������p�h2d�^#��w�:��Z���5Ud�r"*�y��/Hg�<������y����0��a~��?���a!���W�Q?(���l�u1_2='���yw���f,� ���d#.QsK�Q�d�c���r,������9�:�����= <I�I���X����"��5�1F�\���"���6���)5�w���/8����6�	��-
/�Mn\Kc(Q������u/3o0�b%g���,2�C5�S��^{�$�)�U���Gehq�!Y�=Q���+E���d����0.��
DC��>��o3F��)C�0�����\�DD y�VF#�����X�����V��0t���>�zW'�
�Y)�*��)�V���(0����#Y'AEt�=���J%�%<H+�W��Q��0_�|�,�7�,u9�	E����v���(LE�N=M�C~��$�N8q��d7���s�b�o)��"q��4� �����\����~����0\B�N�:��(���g�P����/���T��HO�~E��V�:�=�
��"����E�~���R;���N�B���-V�&��.[�B6]��6o7$7����?"�}�j����T�^�����
�}�&�{�=��o{������?���p��R_��2�:��U���;�M��z�f$L%��u���D�����X{����qO0�
����V����\��� U	�4� ��S��I0>;�dA��(9?LO����T����,�C��[�Zc{����}����(�r�h��9�2h�i������<�[pj��������c��\�|����.�p
����=����<�	_�����|`�����}P����*f|�3iy��0/)��'�eI;h�9�G�8.��'�_<;m�`i�~��k��F�~�NJ�.�����WX����-��(Zks�@k���(��
���_g�4Az�"3eV� */�J0��Ja�B�]0�E��n�����Dn��=_i��C��`p�T^il�0���6n�E���y�M��?T�����NRY|!��\�%S�.U��^������;���ja����v��%v	��5��v�u���������qKU��[�))���B^�2�R��'M��v� Z�!���@%
�U��1�I!1Z��A�X�;��g������XC�0���zwUz�/�a�vy�_���������'�n�����s���[��?�
�������V�V%P4��v��!����������	����:��i�Q8���F=jC@�O�y2iU��KrG7�=��*<��P@%ZY�U;��:�X������)"wP#��\���3��g,��o^��;�}0�P�
�[.�zz8��<�?��T�����9c	P X��,-���������SS���W��L(�#����,�jJ�C�@�� ��4\�q��<��nF3E&���H������.�5�P��w��>��7T*��
�=��"�M���V�,�h�t�	?X�Q�@>e-R��9�if�����iz���h�M����<��;�g�'�m�XQfT;�J���w�����;�&����qTAFY� �H<���b
��}���9�QM�6t elI�E�����8t����-ZR5�QWf���x:I�xJT�O��~`���=���h�������&���������
�����|�v
&�%��')����x$�������&�Mee��H�����r�����KV=������K�R��KY�W������+�+��G��Pw�w��O�g�j�����8���
�~9'�9���-�������7��8��A`�G5�*�[r?���2t�Dy"lO��M`��-p'���3������6�3���a�.<$7A�x�Qh���
�p����gbw���;����,�����������Oiz��e6W�8�|\�8�q�,y�O*,D���:�r��p��S���4CX&������abI��l��������V����3h�����{lB�w��~�ViH�;�p�8xn���q��5V�_s�')��1�o��_������A��P��q��XFGw
��T�O�-�=�����e��
HPf9�1����tc����=�n���=���l:��^8��
94�������Z�A��������^��.U��>�!�fTc���gu#�.S�,��v����c{k[��z*���Ap+�"���Q��O8T� 8��:Y�2�������-P/U
��b������X�iP���e:��T1z�h�h����R��>`k�b��)V
,/X5T�>52�
Wt�uh$c�,�O��s���FCC:3X��}.nJUC��K; ��*u��2��,h�}��O=�
2@44�X!8����g�PJ'�38�S�]1�<a)�[�k��)7}�)��������1LN�F
�j���k]�0o�j���9�����L���5��I������>��������Z����%R��
��{�|]�s�]uN�X�X��.�Ta��Eh���_=��	_�}/��g����s`�h�Z���u`��-X_�u��_��a���%~vA��Aw����,��X���a~q��g���M�����v�f<^������2������gjfL���E���Iz���okn�,qA����m��vm{G�4����<�/��'�k����#h8�2��Z��T�)0aL�!��W�0u��`��(�d`�!��4���t�9�A���I����z�N���j�E�wbz����dq����7�F��R�E���-�6�L���h�	��i�O=��� ��9"����!1�[	�&�)KQB�H�r�~�\�jU|t6I��bUQ0����{)����g�b�zSa����/J��n��@����k��u�v���4��;Jv�9��|���<���4,�V������>`�����}H�
�_���28����r�"����� �5�p�{�1X?��<�j����V'��]��
Ak[��t��\��M:��^T9�Gm;��\�U�,�	Lz3�J�m/�?��
~Db����6%N�|��QO�����$�r��%��C�]�P�ETKT�tl�<ExIk�I���,z8������A�~����t��/��F�!Q�G���C�+�(�����YrRK�U�'�N�'�O%B��-A��n�bS_/%N:o�#5��P,�7f��2�6_�o��(DM,����@ *�G���|/�����k7�����hF%��p5�t���K@$1�����;l��A�@|����&��=%
tg&����1�����2��z���22a��&��^�i��
��ef���b��=<O�f�����b�M')*���Y')���!�slsk]2H���\/1�,��j��G���.�@����M4���=���������>���{�j���Zxa�<�G�r+��#l���Ge:�P	�����vi�^	�����fQ�1X$-��|~}�,���L��k\�������8�P���y�x9W�K�'Z?��]yF%����R��F��FG��������"r"�����sr���R�K���a#�O���2���5p���lQA�S�`:�4{�#�l7@�2��8�=Q�
��P@@��J6h���>K�����9g-�Ns6��Z)���j�v�����d�z|�	�2��]KnN��^Z�r����`�b�D�s�U{c
(��E0-��*m4����`���e�6���z��{q�w��Qoi�������;2��9����}��V�Lg�=�e��g6��������w��}�������~s��3����)�J`O�.�����}������y9X'�`l\;T_�����S��^��ED�,�]O@���PV�c�f��k�Y$����Ulh�L������R~����4��R��]"�[!x�<��)6��
�v��1\����k�S�y��5����1��	nw�9a���
;l��S�����M5F��J*����q��f���y4-���&�t�]#�k�K�Wt>ub�a9�4kzS�o�(��d�Y�#�,r�%$�����tZxp�]�������'<��$7��wR_Z���xZHlq�I�TfP������t��L������<)�����y����_q��\tqO~���c�2@��<��c�����%{��X���a����8R��5����n���>w��2!�z�/['KO{$_�]��c+���KTK�L���:] ������7�u2.�����}���g0ZfUV��5�}���9�<6���c��%,F@�1|o����6+��`���'���u��PG����.��H�S����0#g�������qai��!:���������x�E�jS�V���2M�$�q�����:�*f4}g�E���f
��H�����
D�cv<pg�=�W�L���J�X�}���w}���e��=z�����_�rL��W6~M��S+����)m>��^�����xP�>kUb��^�,TI��Dz�����a;��[�Rj7����?l�{��id�(>K��\�L�+	��nv����8l�G�.X��L�b�E����������\h��
�f�|5����m��TNd����b"��O*iFn���������W\�{^
zW�������-��o����6�����O�ic��d������h�odQ!)'�%9<	I�I�@9j����ef0\$yK�6	�z���;wM��7{�%���[�V�����u�tx��l��CS!��>>��.J���J�@;3�+��H��U�d@CM�����.q��nc�=X�|���X�N@<�����i���cc$<�E��4�AY�LF:=c(a�����$E�aW��!Se(��G�
p����[&�h������+�&��?�F�t�P;I����Ft���;(�=j������o>��Z���� ����3s��F5*v��/	������:����H��f
?(VQ���6��r| �h	�hp��<V�p��rM�&��S�2�/��y�NN���
'6��B2��s6��{7*�.[E\�����9W�6���:����i�h��gb�/��]�����?.����BEM��k���)�Q���#J����w��^6�B�<\}��XsMQM]N��`�%o����]0	�
�5�7�\wU;����K�s���<�^w�����W�U�}t����W�2U�_m��f�<_���~���F�u�hW�"���NHy.��,.1g��J�t����\0�Z=�y�a�I�A���Qm�j�D���<G���8V��^��
�J�lWdc��������?gn����C��Xn��f�JHLu�z#��
�
�u����U��U[�{��:"	_��n�P�z����5�a������&��-o���������f���T����m��������G���I��]�G
P]�g^���du�K���6�u��p5�
�`Zk��|���0�g�������2TSL�>�Ul�;cE{�(��lq���t���8j�SVv��p9��Kd,��{m6���V������}�H�vg��K��G>��]�$�b�y��o��������\'�$=��@�v*��i�(d[���9']��Og\����K��o[~o�������������������!���E`���gxX=�������V��W�����|lFk)eU
S�9�z���eO��)?����p�����,����?[���cNx�0�$�}&�&[�<���(�h%D� ���8�*����Q����X���y�Rz3�g`���ii�_�O4�����_H����!>w���-��s|�o�����=
���S\�i�V��
0���}��0m�j��U�[���TZ_�����O$k�z��c?g��1�u��k|H1g�o8��9�V�h�j��Z%;���k-H�����<�a�������u�S2W6�Z5�R�e�*�R�*%x���
m�\{����XN�eX/��N&(�����`z�Dv�z�.5��T�����O-��|���+����u<(�h�����d
k�T���!����0$����.�2���]6��*�� �t�����
���b���-J"n[��A�`9��3�p&*n5Nn��,.��RV����%��[�`N��������br������3��t�!gH_���]�91K�*E%3=_szk���������*8�`C+c�qf���?����
���XO��`-��o��O4)�����a���J�l8��������?P��"r��@X��3[���LkX.��v�\.jk(ck{��
���"3u�Vb����)Fwb��M(�$�
����8�z���w���Qv�$p<4(�&Nn�����h�_F�����3�d��)����H&�^�7�;�dsKA:��,c������(�) �>����B�����9FZc�)��C2u�s��w�!����N�A���(�=\.N�v�/S��|�J���g���Y,�����5-=�p�d�������N:0�r."�.�Z���V�;�pS����A[�wROw��D��K/�/���b�n+�RQ��/z�7��p�R)��J�"R�N��������� `�IVH1��(��yx�#%�g�a>�� �F���N����j��@�Vu���3��xV3LPH�[���DQ���U�J������&.�NI�K:�$F��A�wg�ky!�+~���E��>]���W�=��\�&7J�&a��ov]���J.�p�k9�'	�H��_!At��@A����}�c��?/yj��1l@m�����(&��D����db����������a(+��!������������C��������Y]En��^�v�V+v����_F~0�;��bcD���*vL@�������p/���j���J�����'������#�~:�~g<2EL����FC\��UXw> e���y������j������,=A����[
����&����f�P6����\<���PJo�,�r#��`4���^^Py��;!������;w/��G��PJ�H)��,�����������J4J3��r��A=�ij��A�a���K_�^X��b'�B��JEC"g���u����Kj����{��1��D���X0_����������7t��Jy�{s�t�����3!)]�s2S����/1��+W0R�F�n���]:��H�O�\|���#�,35��JI��,����r6R�a�N,��X�_�_OT������r��+Z@^]a��
��k���s�6eQ���6Er�u��b������LS�����g�|�5�f9���]s��47)�g�)��
Tx�*���Y��M���=����%f���f���S'|�E�k�Oi�w'�I��V���E�t
�<�@�,�qB��w��n�31�������AR/Be�>�	�_-+�4��C-6_W���b���6��
a	�\"�V��m�C�d7�zfk8��v��0�>��^����k�����9���~�%��E[�����(��%0��D��.yGY�AL�E/�	�D���0�T���R��k���'l������1Y��z"�>a��K���/�g����N�n��^��p?����{4b�\�����,��A�����QG��;�B�L�����V�a�@�bR��Wr���6����9%qs7c�����Fr�gV�3]+��k��R�X���cr��
����Z�������'^%�)
����-#�#3��`x\h����K
�fE�=��Y�S�;N�8�T���A�zw�U��@M�������
&@�����z�s�+�\��A���%�`}��&�'�[��m�y��{����_)�V�������������Y�?��H6F2��8�����i��H�.�G��AG�~����\
2�qT���n���G�G�&�~�5�yg_�d�<nOy
�J�������'Y�����*@8p�!��f��
�V��;D�����Ml�CF������jb�7�K�L)�+���$8��y�|C���k)kG-Dl6��!�z�Bl$���!�p�\�;;aAG��������t��/O���?!:�7����m�;�*��7�v��e���v�	��
!��m���X����
O]w�a`�|����z?/�s���{������f3w���+�I���=G���}�����S(%�6M"/�*�Ck���I��
���c�@n�I�<s�yn���8���5D8r���W�k ��w�1
�������(qH	��tB7�2v3&tC���r^(����}��-�mB����K���D�
E�����ln�71Z�S����=U��'��dSF�]����h���������Z���W���8b�
��C������T�������UD���ZW��>��e�����O<���`_��7MYS��v>��K"�xI����Z�>�Sw���^�����a�M�Lom�����&v�P�����DS������T�c��'�rK�h;�I��f�������tP�X<-}D�B��~��t����4[�5��j������+�����9w�����7F�]L��F��8a�������#'������Yb�\�A�����dM�M�t�l7f�Ua�������"����nW�YH}�P��C�����i��76*�{�4��:�~��
0�1����w�������w&�������x���aC�+0hc@$/�biR�0��I����D1����[ ��?u�>i�0�����w�b��"��)�qk��L�����k���>����9�>��c�|�Hn�=V�C��qW��`�_�~F��b�!Z6�����.5��E���Y���tg����,BpS�9��>f�~������,=(��V�L�:���K4�:������V0�y�ri�cD�h>�Vn?��S1	AY�h]�I$����D��s�Y`������u��N3�����MM$�A�o�Y�s>}0����ice�IZ��������{�&��H������.~�+��u�w2M�oY��������h��/��Q�q�B
����8|B�L.��	���/^8v��!�-Py��0��C�TF68�MzZ�������Z���v�J�����2_��I�U}�_�T<�����@�D1� ����]��+��cBQ"��?����(t�`|-K��"�����8��r��L��RUvK�����_q���/�v+�oU�)�.b�(K�HV���;�S��A4C_$��z��0|�e3���E�31[r+i��
�P�y��,�<r���M�"E��s�scS3���i@��S�x���*��x��\K����D�=�zy8�8����.U��rHNI�vEQ<�������3�q����L��}U���[�"������,�Y&'rj=�$il���kAV��s�}�y�&>K��hK\Q;��"t�Yh���B�b4�54���7.�!!-���S���J��po���U7<l��4�C��:,���H1)��-��#��J�*L��]��c<���(�@1#��"<�h����v�Mo�*���kq�U��k�� T1zd|�u[��%��r?���w�
���|���/���%��GB��H�f.���f����	��U[�T���	<M���Z��S�/T������S�	.��M���A�o�I��`r�{� b��p"oLK�>���\���������l��'���mX5�ZQ�q�`����w.X��C���%X��P���c>|A3sd���o�Y�yy$����)����F�����.����(����V�|�?$*'b%�V�h�z�ft4��c��|�V-d�|���b@�M�[5���|�*R���m��G�"�@?U�(����v�Z�B?���A���tg���(���+��H�����=i`Y����W���bvl�.���apKH|�;��*p�%�+�g
{����sW���) ��Z�<9�6b�|@d��
*�Be�P����	�R��`cD�V�f�Z�������_(��7�8hJ�Z���������P��[�'�����6��������efj�e��I�$PJ�V������l7�hRe[9������"���H$�����{�=������%6g�t�#C�:���(/c�^o�������������-����3�`�7B�0�1\��6��D1��NNvh��V��=�9S�����&�=�����]l�x$��l�e���*%-�����������5������@���J��2���@ITR����g���E�}��S��[�i��R"h�S4��w&����Py"T'��F�r�5A��U6u�uX,/�S����xz{�n����:KQ�������<��8��t)�#���)"����Wb��#���<Ax�!h�sU����^�L����!��rD������X�K�|9q@(�BD��3QI�(d��x�@Wi��>�u�Nv�Gco�#��9��@{'zH�g����Z���zc�
i�`'��[H�����^����]*W4��n�F���l��p�+6���
����g'����>m����:�����
��W~�F�$�l���V���h`[�
/b�����,
(���IQ@�~��7BC���u�{�%r��c\��
��o,Tk���h(/�f&U?5Go���k�"
f����{�`A#,��� 
�p,�c<V�����{x�?T�e�{/`�0�1�h2���7�Q~����*J
����/���H)�i�%;����hH��n�?�r�T������>�FEp�D?���mX�C�J�I�V��:�s�3-$ib��X�K6y�"�v��kE;��3;�k�p,�e�����T�pk����R��q�]�����a��8T����#M��h0�#(��2�)F���P�m(~C7�E�C�3�@s[N��*�az4l,�B'����/�����iLYq�PL�l��EYP�����NCHSVr��lY�cl���9k�;�{�y��.����b51�fPS����G"�:q���&5E#�5�B�@Q��#6s&���;���j\{x���.:0kg�.����
�����YN��6�.���]���c}WN�����]	�����E�r���.\��0B���B�p�	z��""w�B'$�����p��N����w2!��)S.-6�j�)�I���ip2�s�����p�w&�A���?������g��<�:��\�2�SW0 ��)���Do�7� �GH8������/gg���osez�u���N{�������v�
�&C������LP��dJ�P	R���VY�Y�0$Nc`T�7'�'.S]di��0r&���(�Z�����+V���~.g�"�>x!�h`%�0a�N���G�l�N�'��gtLx����Q�Z1&�Nop�=<dj<��(Yn���[&�������`��,��p�9��>�����=��A��G�<���H�x(������?xt�����n�h[)�#�p�%<��Kx�P\�����G[nA��"�i;����������*��T������j@^j
��3Ix��I�/k�m��%���E�C��,�i�����.���.
VX�QP]-�8�(�#�	S�����xt�������
e�F�#.�a�I��"�����([+]���{Hh�(�F���F
)���S�11���[�3�,[���{	{�p\��tr$'OaZCa�R��~�GL�D���]i���?���%�����(y�u�� ��$��&S_��j�o3�����Jc8[%�k{0�'�%z�U�p:X�����\�Bfp�V7z�������{p� M]T�J_B�;d]4�������b=�P�o�r\�#6� �s��C����;9���kQ�����k'|#�SdJ�^�G[���6����a�'z
��J����;�V��X�OD4�����3Y�>C��5�U�m���q���`��5����Xf�����eC��P�YS%��yXA�^���-��!u���!�]��Z�}���!*�|��V��n����db/x"i:�*��]����#b�j��R�C	��<
Ix�p�����8c
~�KV$9��i�-��	�B!���5�������[��[�^��=7.��Gp���Ab��� VV��6��������0E��:�!����0$E�\�Tg��~��'j�����b���2�t��Bw���qr"�8u�N�:��vE�9y8v+�P8�D�v����fnc�n@*�������(fE{/��$N�A���S������C��BH��m[p�h�����sK|�G���D�9|����>��j�eW�����`H��������'�.�����I�>=�1�R��.jO5�xJ(��F�r�'���]���1��X���@�c���z�����/�s!�T�t�^����>S�w�����~����=��~�~�=�I{o�p��Ew~�L���I�������R�WMq���d%���5���� ��!�/��B����Vz?�tn�
zj�@����}~2����������&���zzAQiO�y[�����h���~������6�i���[i�-}�V�U������UGhkO���?���w�oU����c���#0?|�vg���Ue2���;�^o����N	,��1�WZ����>�*a"��(���{;,(H���G��
�}�T���IJo�/)���Y�?���\`	}E�-���UHg�����/�f���P_t��S�n�����u����m�>z#����G
R���tOj���W#���@���5�h}��A	Zha�3���_�/������"p8���S���R��XS;���H"���ty��#+�+Y��S�/
���>I��WB�������.�KX}���P��B��'��OZ��x��������H�0�*��qV�LHHK���P6<Q����tVr%���\�]n/�r-�U���:m�	�-��$�*x�B4���8���J�9!���J���z"ZV�%����3tv%���+X��m��p+���Sb'[�%�s�=j���i!n*�K�M�zA�9-,�6jIz����Oq�������U���R�9QK��}�`
<*��9�%2el&0uQ7q��A����g���a9(y0�@�Y	�F�������n�.`�B�l��Z���Tak��D/9��o����������c�E��,u_I�b?	�ubU�_��;�x�b@���V,o���
U+bi��?|Xe;�������BG"}:	��MJ���mp���H��/�*��h3�@�V6<2>��Q�nW���G9�N��Q��#�������J���v��.�������/r�t���*
�B�dka�?!,6�!����a�{��v�ROc�@m������������������&���sJ��|��D
L`HGt@Z�S�����A����p�b.Rd���2;�!�d��1�{�>Fu3��J7�Z�y��t�\�)��*�=��1�<`*��I�����L�@�1.H��nH��Cu������?��(%�|$����FUJ�z3O0�Z)���Ib��h���0���T����IJo�6���%����d�;�5d���|z�P��J9���u������	J�p���
\���:���$����'|�d����;����6R�����X�������=�ug8����*�@J��kn�/�7�������l�,>.-���	)^���?F�@3Av�ND�E��f��iZ�O1S�<;k�'�U~0� �<��7O1q�*�$����R1��-a�m1z<����P��X�{q��D(d�Jg��,��}? ���x�*�Z��r�T�����|b����(��;����:���uMUm��O�@�|�Ql�����0���='��=�����G�$rbk�BUX(?M�3X��r��H�=��z�;�(��,�v[2�yk�(���y���u��QI��KX���u�f3y?x*����������]��R�{*\�M�B��h�/���k��e)|�j��S������P��M�|�y���Ta�^�w�6)��q�`v�Fk$�$
�v�V
�=��T;z���������O�r0�ko�S����r
��p�Kd�1��($��\N�qnR�7\x��!_�;�u��
���u�%��K�k[M0��u��UM��@����b0�����EOx����2���f�W������dB+3�P�I�=�NA�"�$/����1���^z�#����������S:�sPU�'BC���]��������������i�e���<W��cS�2X�}����c0>O�E�����#���%pZO���t�'��~
S��
��5
��)�5F.U����;��e-���Z����u�qV7:�����\^7:�����%I]�N�0p�]�0���5dO�1L�����C�I����������O}a��������2�TC^h4��n�>6O'a�}����N�$�"���!�(�����&�(1�_�5Y�	uD�G�p@�b�y��lj�8N*���F�8����	��:D���sD�:uEcxB�<�@A�!x0 ��Q'���6��Ye��������.]�4�g�9�4':^$���<^a�o��K;<z��D0�U�6Yc�]}�P_�e���Q�Z����&��FH~.��L���dx�;/v��|��k����L�rg%�n��KVz�)�����>�z�����+�:����D>��)���m��\����{g�R���:����0������}�+E|3YK�m���*Jo�	�_p�<aC�oZu�=K%��k�	58��QF�Dd�d�(V�Y�?z8^�B������_K�"_������*�/����(����M���%��+E���3��Z(b>���(�������0E����_�TM�c�������:�6���9���3�/��I~$�
���1,�do���:����~�����Z���8�������j~!�Bv�]�+\��|���F.�{gA���9�[��s2*|K��(��In�Lo���������avp�C;���`d�eH;�`�^���Cnn�q�MBSBW������M8���������}��'��Cb�@1����M7��{��nI��m�.�4��'c���N0��P
��$����p��P���L.si�L4)�f����=@��D������]������)z���tC�����;�o��k�{
�����[����������)B�������B��W��ii
��.��S�C���+�]��Q�p���}�*80�����pP����M��O����&)��t�����2-OO��M8'B���o�	9[�=��=l�
��a�������C<+q�ig�����V�C��e��MJU�X
o2���8��
|usP��D.�2�|���%� ���C{>��/�cE���2�2B�����e��JU+���/�M�/,[�M�SH:�%Q�����}�r*�.P��������,���]�s�L��9��ju�����U�}�����}��Ly�L !�1�q�~cr&\
G�u����aB�?�#Hk����{�<�L�B����d�0Q�B�������N�o��M�z�/��"�x�2��L��:���|� ����3���]��4>;���)@p2�=����w�����8Q��G�����
���0�}�����mz,���E�70I#�f:�.H���T0]�l<����U��)b��f�qy6��)�I�Qm��
���@1w��V������
j*��Sc��iu~��^��_��,���������^��a�h������-&F�,f����0.��Tk�����q���m�W��}���e�����2ev��v���m�}�,e�����'�S�������O�:���o%V�2��6��:�d�$H���"A�bB���Gm��q��2[x�LN���7���.���$~�	z)����T����(����1;�T�%�=��@~(�B�/B�F��t�x.�������P�tR�|cT�<OU�:)]Zh�2��N'S\<O�?fUN�����J�n����t�j�t������`���oS��=0��M�F�Kj�-�0�*����\1.r�DNL��-�>1�CM�d
�������3Y�
H�t�j�!�8���/%��=��z��@pd�a�cU.�c���U���kb�?������}���+c�U��lf9�{�Yw����0��$��b*��s>��h[|5xC��020���ze��.�|�=a_J���������QA��M��ah ���PO3HS���q�T*��%BF�%bv���_���J���V��I��&��i�+T
����������Z&�F;W~�+/q@�l�������x�Qg;4��R�k��+���m�Nhz��,�
s���{�#t:�-�RQ�)hx)-���x_}q�l5��8�������������S4a8Ih�qJg�A�t������
Vpl���v{��M������� p����d^��+�Jl$}�q@���0d��
^5Qx)�Xr8����!�+y'N+W3(XuR����'fK5MQV��{I�e��.T�c�56G
c��v>�������O
��H�T�f$I\VH��@!�	�DE�?H�I���P?_sD��`^J>�e�`k���6���7%���e���*cy�Ce��|\�d�Z���z����H�%%��pl�������gi%�DT��E�&���V^��K{��&�y�+��t��G�V�����E������������FM_r����D���1������gES�����+���N�rOi��>��]*i)����S�k�GC����*&���(��� �J��Q_������TdT��/'�D?��f���Ip�����������U�������J��3�D�9��������I�/f���0��	�V�������w�P�{4�������M|��r��=�F�	M������iC#�K������8���������|�p�������Zw�9H��������i����_&����J�kw���@�6��r���@(k���+kzA��4����Vr}����(�.#�V�v�V�HJxy�(�������0��L2�`��4(�*C�h��J�x�����@"��N{8d��Y��X,���n�mHe�-AH���_'��(�L�J�	.T*~�	JP��"x��"��$r�����������`#���
�4��3�e���
�Y:����Z���U�U�r]�������}re207�}���?��1��J��{���6�������Z�L���NF�=������5���wp����c�qz�O'��[(����B�$s���{g��
����dC�O�]��DZF���$v'���>�J�nHc\���	�~|�l���q "7���X���3B��$���i�(�>�v9�P�+���|,��������A������|���#�>�A�;H��Hu]���E�	���V�F����1��!(
�k6��m6�F�,��7G�n7�����	2a�q�|���\���;;��H$N�����F�{W]�������{�7�D����w�R�	w�z%��.Q}�� ���^�1�xejj��"&w��\\��J�0R%��mzK/4�U��Q��F��Vj6i�YH����,�
��S���M������>}��4S*C���n��Z�o���q�����tk�Q'c���S6��q�S�3��Y=�`��l����S'���8����Q6Kuc�=��w���i\Z
�~��C�SqWtN� ��
eL{_�)������1f�w�]�u|�U�L�*�4�KJ�Z�����
����	�
HQ��JRA�
��BQF�.��2]�O�?%S�������Dh3�X�i����nCNf��18t�������L���'u�����
���I�"�f���81�=��%4�s	g[q��F���[(1�|�����Q�5�p#|Z���S���������7x6�L�7���]����z�������_4U��u����6<�l�:�T����7M���������BFa�HF
4���`kNa��w�4�T��e�������X����_b�T,����I-�8s������Fo��:cw�KUH/�m��Qd�"����6b]��m������HF��w��Q�V����&t�(�dH�\yO���W��fz���h2�Ju�����0l�U{p�Br���'6���o,t5�{�����=�l�L����lY@9Sx����iyBl\��q%�����K��)���d��:%=�K.��)���q���-�R,�����L�h6��'�[�lts�6���n
�ZO�3)RQ7Fq�kGh��F�����T�z'�oN�*=�
*���������j����Cu�AX��K!����Dv�{����5�]���'7T����op�g��`��;8J��p8����~�ug������}���� ���B����`[�js=��LD~�1�����+��t�R�V�j���KC
Iy�Z(���g�0&���T�<AJ��r�|��'r0?>f�����8qw��3����juMM���n���	3:���?��v���zs�����*�>)��YQ�{�-���j������� ~k��T��.�5��Ad~����Y�k6>-����U�%@q���J[�k�rb,�_���G�G�	��)��16�#�P!���0-J�Z�+|x�����9h�w�V�A�X�4T��8b���^KF�\��/�9$�t�S�m����P���
��|:YF���9�r@�6�������"�_�bSn��H�Ko����woz��x�b���6�;���)a��p3�"��E�����Q/�����RvrEm�V-���@"�=���df*��r
��r�����q&�����
&#r�g��UoUjv�UU�e��4�BI���.��)��[�)�Z���eeB=�D[T'�t����{S#F�}f$��VE���t��V�&�6Y��y�Je�i�h��!��W0�Q�>u/9��L3:yyF��4�:��rvs��i��f��\�%����b}I���}�d�w@V����6��	�leV-������P��-�����I���-C�\Z���M��$8��j�4���;�v�y�w��m`o�]�c�����]�������5�Z����%#58��>��-={.e�S
1��2n�t�R�Zd6%%M=z�P[/��a��������M�6�_/Pu��l \^�`���	�j�	Y�U���"N�GQ����Z.U�j���K>��*�rC����^���L��I
�
#o��%u���?�LA��}�
u�{������P�����'�i�qT(��!����[�S�+�V�n����5���a�W+V�v�X���|y�7�xV��E���D��l���b�j�GK�o4K>�Ht��sEJFv2��L��YLJ�@H�,���R����'.p!���2����
&������� ���+����YF�Y�?�������I{���}�	�,��WE�'��7�����
���C���|(�������Cc��w���d�O�.<�N�/F/�����njM	������sq�U ����!An]�a�=�m��wG�
�KS��d��N�����Jv�R���e�����)�Vo�e���ZO[@/Z�b��Z��M�L1�h�������^�����S����'�gB��!TS�(6b������,�$W:�!�c=�y�(����Hl�J�KE�~���b������f�
�Y�R��,�
I��,����'�%�����b��B��>���+��x���)����%�r�����i��@V2��Q������	 �V�8
(�,�����-��/��������0�	8�)0�[�^�q>��rEx`�&<i5�%>�h_-{�������<��hb��G�p)�Zr�hD���N!�}8�9��J.������3��;>������	�J�M$�g���Qz���4��	�M��"�SjKg��pm�����l��V�X���l	c>#x��$�R�b��P�
&3�U+�q�V.�m�O��Z�Y*1����7�D���~	}c�����r�\���S�@OM)d��>��&�MD89D����m�F1[�& f��f�?p$�����������"�"�����:����X����c
�Dz���DpDOm
�DN]����V��R�(�-g?��9��	��#/Q���eu��5LSRO!���(����=�u�X�,�O����'n�P�Kd���8Xu�B�*������������04�P��� ]C�"��|���n"��wQ�m��F��"n�buR�NN��1�/(�%��3d.7
�)���T���r��R�jQ�>�*Z��&nK)�R����r;+w���%��z����7�:�$�I�y�rssr����9�c�M!�K����P�r)*�H<A����3��=!�E1�s���&[��i��v�W(��<i'Z�c������P�	<6��>Q�P����$"]��������%��9���1o�J���U/�������1�>T+H�����mSk5L%�n2p�WC����I�c���;��5
{������1�!�K����*�r0�U�e-���a���}2��`���s><d���o;o�]C�y��'5�����zgM�K�sY2���$!�&����P�����x7��YA��L��l^�8J+j?�����)�7j	��o�O�	���&��oi�YQY.������f�Q-2��~���������^��:��.�s�xV:�|���������K��K@�`z������D���O��d!Yg��3�{Z�O�WW��FO����2��H��.����-���$|Z�CX�O������4X���Fprk"�sX���,�.��y���Pq�I.��h
 r��l�<,�+�61����^W��)�y
�C	U�M:�&!(L���3�#w���+�q�Z�IC5�m<�Tt��N�����Y�?����h�ys&f�VS%���g;*b�,�����u%�0�v�mZ�q������0�dq�����p�m:�d1��.���1��Mn5&Ke�2�u���K��j��,��!Db���0��h��oN�b�J26�O��	:�(���p�n|-!�eE����R�����������i��qRo�b7�������U�����E@���~�����-���>+0��/���������R-�_R^�1�QA;��=�p�B��s�
�J?"��,�)�6&�d�[�&�J�M�U��rExW��!�a�(O%&����Z>N*CW,��{{�o����P��{Q�kxa�c��N{��#7I��z�r��m�L&�����^��jt�m���^�;�&Q%���G����23Y
hq����{���AA4k[2s��lL��p�q��`�P�!��X�9��6dN�G)��1HG�q�a,t��q�g:�9d&�^W�[e�� ���w��G�{�p|=��Y��<s-a���@e�gA>�-�	��,[urG#��=b��*��V��9 d���
�L��C`����8��Cc����i�h��w;q��W2R�:�a�.�|B��S�8:�g��&"��V�FUX1`o�o���ow�;�@�8"�t�n�4KF4Bd+����7�jBd�>��hn�L���'��tZ���*'-2]���ZT������jG�[z��P������#2�?)F�g��r>�"�j{���Qi�x�n�� 7���5R��OB��C��=}�2%��X��p	#�R�Exp&H_%���X�-TW�iq����~BY��G�+'�|� ���A��M������X�������:=z���B�T�0#!8��X"%m���&�4	V����
���� �;�����$9K��#��:����Z���1��p� #a&�,9��r��p� G8"x�5��:\6��V7�����>���;G�87�l+90GX8r`�������]8bF�D!%���JE,5�M�"Z��
��	���{Z@�����`��?��cV>M4�V����H
![q��#6�L���(��n���7����H8xA���;J5c?����[��3U!X������b��M��s:���,	�i�v���7!���|�v�:�_�V���R:��������*E[�������]m���I�YPZo��I�q"X�������$��D��#���`H�����Xb���|/Q�b��������L\�����UCL���J���-���7����cU�q����������n��xW����{��a7fq��xxh9�	iz���-�V��b�3������A.��#�a�j����q�}����?�K{z�e��j�[W�1�����s������E5���R������\�}�}�=�%2+���_���Z���Hh�I�E�
��$rC���cNZ��J���h�p��+6a����!�{���q�X#���da��ph(��/�p��wf2�u�DT.�nC�-#Y��QN%1��$��&�����,�����b�L��|dT�%$+��#%�
_1X�br1o��������N����+B�p�;��H�������#U"e,���(��9�Le�B���Y���A�/4�pt�����~�����K���a{.i�K��2�e
��������XNq����0)���c��d?��_��t��?�,�������:F�~xX(�\l�mO���A@��O��Lf��v���e�5�L ��x��P��q�C��q���Qn�����Q�A��Mt�C���RRH��M���uk��b*�9��s�s�t����'S!k��;���9$Q&u��^�������T��+��qh�������s���7Q�#�>��(�YJ�j��O���0itj��.7��m�&I'@%�C��Z�\�UdM��Q���fc�x���9q]���G@�}"��u���/��y����
�p 4�_Jn�-�����K�����Z���"����K%�-�C���;R������0�����N��c�B2��'��#Ib��C��"	�&�v�T���N����?s'�-�E�A%������M�x�p��fV��	���s��L�2F�������ix���,
P�8�-`��rf7tA��"e07VO�9
�IX;����������# �~l0��_��z�������'M�����F�}fC�8��)�IG�R;m`��!��lQ@����K>t��7�n>��k[���nEe��C0�6/��u�Az�`${���
�K�/Jlm���aAtM3��`b������<�����O8���D���
����r9�
��e�T��V�aN4��]X�����3M��zr�3��AUc>�>�O����n�������	���@0�}�wST���V03;a0������q����Y����jm��i�O�Jt��������^I�Fc��%on�"��u���oq�H���l3�X;�E��'<7�+�/��fyA���8�D�"`i :1<��4�6M*=#���
����[�Jz��,a��:�R���'C%�J��- �4����`����MCF2�i�����(=(����M����U<N,�*�3���&\�V�e>�h�cn��f1JT+��/WRE��&�4��0����F	���a�r9���V�kdI�h�V�T��0��s�E��uA�����mYD
Q���#�����=�H�.U�U|s��wV�
������V��0�9$XM�{B�p&J��`���) �IX��p�@�����Y����[�������j�I7�N��������"����Q2i�npk���x�L=��[�2�9&�x,�?�k�H���$Le��q��
6���L+��?����y�s!F��0"Y �x�.�aGE�p$�F�e�-�{DO0
��S��Y��R����a�C���{�6y{K4|������;o�e�����������M��Aa��aM�d��d:d����]s;�T��>�t;��oRg�m����XU|����^�z'���_�
���&�e~z�J�d�G=I�@���"1`��2��Fd]y"�����g��������5�d(�A<�0?��y��Sbfb�����d�eL`�����"���1�`�b��C��>����Z$�R�8FosPL�>��v
{+��R��%�������x�P��e�������q�����jeS����3��n�eg�1w#�5�R�Y�+aUJ����S���F!�
)�=.���E���y�(y������4�K�������'���Tlfl}����������7�6=|�#,.�p�(XH���3Cu�p@�x��o-����C!�}�I�B�e�M	w�N��REMhV-��jI�5!�D�{�:��D65j�0���7��Ca�E���F�����l
;���"j��|mu��jv���p�P��i��w��
����hk<u������q2E������:.���_��7��x(2k����,Ho(P���� j��K�`��������s�-^�Hj��=�:�9�2�V�J
��h&q�Sh�>�I��+RB ������t
��q���V*z�<V�i}��~#���./�11��7��	L�B�	�N�m��a�=���QO��+5�dZT2J)q�@}�W�����Yz����s��H@P�m����T�����].�v�T��P��;9��b��>��U0M5��IPD�U�r�#8]���=��d�h.���=k �@=p7����:?�����v�����Q�R(Zs��� FL,�P���2	;=�� 
eTJ�������:���Z������)#�����������qR�>e�1p#����1|�a������Z��7�k����eLa�8���I����3a!�B�M,$u�X/T�
tE���n)�s�U�V�"���P#RS���� 21�]�>%)G�Ei=�Q��t��:%fP��PD[��y����9)�E��3������r�=+	�8TY�hH#0KL����;��3���DI�����3��W�3;��?��{��\��JY\��TxO^��fHy�h�V�6��Y�������K�#���������|�a��E�+��;'��v�
�������U�
�!���=`z������"�����ny�2�w���p�"����)�����j�L�i��g������;F�T[�7����B�"q�����Y��
o2q�q�����'R���I�|@|>M����}~2�y�K�1S{Y�1`t{Kf��Yx�g���<���no���lu(F�=�hM��$�1R�*��5q���i`�������
G����845&�H����\Xm��vY�V�s�b�,*MTS`i��l�L��h�/0%����]'���zBe8����_zWK&�T\�8�H~�d�@���]L���x��N�����;����#�~~@�J����g�?�	)a��Ry�����7�m%>{�QStr�������gH��,_�����A���R�;�)�"L�G0 N��hOv��P����<��@�X>J>��Hm���%(6"������9��6�����?]��gU�iW�T�
�������[�E*��]iT4�XL�]�3aY8I��d��4d
"�Q��qyv��K�l���S����+�s���B�:{�������Wso~5�%�����Z�n����59������-���Y��{��C�|�o;'�I��|�����Nz�c��>�&�����c�o�a����3c,�����o�gg�#��Gh��#��}�==vFg�E<��Loz��o�i�mG\�O�o���'��>�/�}��U�����:���O)�!p/�}�-v��j�~�?h�h���;�
��8�^�f��`���J�{}�p3bp�pl| �k�yo�0��O_w,�z��	~���������w���?���B���N*8i��|�����F���3��U�����o����0`g����#�0��
���������^���������w�L���;?�0.6�v����=���V�s��	�����i;������E�b7j������cr�1����-X��K�y�������$W���-�0@���+g�Mo�7XUa�l�e+U�>�V�nk��Rz
����i�''���O���rY��$����O����y��N���{pk&R�N������'�nq��-�rZ&�V�c�`s�_�w<v������g�p<v��./d��0B�>	��_��)����Y�2����)P�	7��{�L-'��j"o&����#�Z�(�a%J������`��P�������!��J�Q�jdR�n�5�p�	��7?xA�o�E���������M�@����u��Z�x��;!W=�5*i��������X�'s]��|�ppV�}c�����N�a-U:��I���P�t�����Wpv~Fn�)�R7A�q/�hz�|� �uWz��L {����`t#+5+P����2��Q�C*SDE�L)X�Bu�����\
`���a5���
��b@<�����))�Tj����!����9��F�&y��@�\�B�a���^H��M��F�6����QS�I���kv�c��	�/com�Cx�3&F
���W)��^�Y�c��/�a�
�Z�\c�c����}��sTb(b&"�XB#�n�`��)��f�RV��JA���<%BJ�9	�4�����7��F�����u�7M�V�����`���U;X+���G��5��4���&#a��^��JB�:,g�����?��Vj��W�F�(��>F� ���x}Q���|��p&W|QI��IP�����M
�������x���a
��y�s~z�{��C����X�TT$Y�����W=}�ys6�����T�&YO�Mz���S�,�wM���`yv,[.��F'�
��L�X5g���x�Q��J�A�n��4�{fm���Jw��B�Zv�!���J	1������1v�f��4��������\���F����X;?���(���d4	�����q��m��������T���7s��s��L����R��Z�w~q�V��h���	qj�r
�d	N
���&;�����C���;�%�^x�Q��������h�TC�;�a��g;4��OP�[o)��-`.-�jp�r���
o3�i�#� ���"9=�;�lbu�5�����p�X�~N��e�I%F�����Z�U��m5bF\���	��(�Kw����Vt���o��vC�C���?��j�\�"F<Aj�[��w�����/R��6�,��j��q{��~����pL�P�-}I��W�b���{o�~����!������`�J6���E_����H�:N��r�B������N������'��� ��=���aZE�0�o�_y]U3��Y5�~+����(JG�&1��e�`������~�{��^F�/5���hr���bxZ���N�z���z��q�����l��|���mp���������D�R�3�iQ�C���p���T�
�VmTk�M���	Y`r��v���j�m1p�V�i�[%�	�����=\M�%�x�Y���MLB�}���a�W�T��J�a����Z+O�%s��3��6���-������py!�H���o0tE�����|�VH�?9��[�u���k�V�$��o�k`(�F�\�nn���Bs\�4�FU���8{GKf�{oK�t/�$#M]��q�����L��[�s�U=V�s������3�1]q���jV�-�YmTehd�J�f��"����If���Q����O��=Z�d�DXD.�&kj��`C�v&X<}b]�J�t��c���������^*W����*6|$BB�C��c��k�T}MD�2C�U���b)Mg�$	��9�<]���S��J��
�]R��etj��>Z�4z)q�*�w��{�����n��'���<���z��O�c����
��w��{?���5�`�������s�d,�}k�z�lA�~CJ�7�7��NC���+�^+���h���0O���y��Wj��<~�,������������KDF��9�MV���y�j����y�:���"\���C$d����l+U�z��Sx�[���=�u�G�w�N���u!x6�$�u�oZ!��k�:��GA��*�U��Y��(�j��rQ���RarJ�_��i��S1���Y9R������P��r2�������&�[��a�����f������s����gg���s�B��Q�)��o����1�����Nd����/=A��=�,��<F���IA�{E�L��2�"y�o��>�j8>������i�J�`����W�O,�z���*�$�w0Co2�y�Lg�Ft�E�\h�C
{s+z�QyC�3��G��������/XQp�	C��Y��'�D��;I�A�d6N��:�/�B;���y�T86t��h��%.	�g�V�;s�A�������
N��L��9��������D-�(/�r��<5���o����X��^������M\Si�qL
���u�3K2w�1���!�@�-r�����wt�.��5l	[�����
���U�.Z���!�
�-Wf�2ui71���-���c��sh�k\�'T�&���99������\lm�a�=f:0&}��)~����*O�J��0�ef1~+�����4hG���LwoN����Z4�������5���~S'�\bko��E+�[�����m�T:��Co)��Y.�K������d~�T�b���wv&���\{'�U�^>Tj_S� ��R4)s�c8�W.F��6�A��B�&���>/�G�`�J���H��S���c��*:?Q	����'�����:��d=qn�DD������{{F�T��?��e��y����]����qjqqP�" 
{�]S�1:[f��Q����'��:�S�2���z������S���0�KF�
�N����?
4����j(3�}�������X|T�ha7Y5C���D�h+�IC��=���xN�/�0.M^I���5�r=������n�/�xuBq���u*:�����8�����( �HG�/�:�fGt.Y�b1��K'��� ���tk��j�n�EM�����e��n�{�R���l���2	��x��P��
X������3� �
R@��<2�K��G/.<����DipFp�0Xn
�-����J"�B��h�U?�D�/q\v'}�>$L�G�]-5�\|�bb�'�sp���Y�,o�}#�Cr����a:�����1bt������L��3{�
N��r��
�=�H��r�
#�9x�#H�{(��)Pc�bW�E7��y"=!,��m�������Ms���5��z�.���L��mx~,����h�����iGA�8��1B'"31����4�����@�m���p�k����A���{l��<C������.��K�b�b>�g���P	f���G���\�������af�0�r�%�>����n���p�U�Pb�P$D���z;[�����/s�Ze�T/j!�S�i-	%"���b��<��;oaH����p�6.����P170�C��Su��P��np��^H|y"1+Mh<�C���M��j�D�}a�4�@U�`����q���m���B�Xjp��tM\����
��;�0F����5����]mB�`������#e$���@���+��.��e T��E��
�<z�JD�<�	^3������@����%�����hA0�`�<������I�����x���{�)��Dr�3��	7��w�
�3�d���Hd��+E��GI]��+���}�c��m!�I8vT(���
�gg�r�!*�8����E'2-L%e�����,��3+D�RA����'&�/���x
}��]k�~�m��L8��q�������r�Z������@��w�d�J����4�N��Md��T*�����������b�����M���oO�_�0LN$]^X�
�W$[,�X�������PK��!�b���'2M��1Lh����������!*x�u<����M��m%�?���g,`�,��������'� ���D@����D�(�c�P�SOgZ�Bh�CsK<����e�lXW����L1����M�,C��.�S9;�,�(o}n�(WKv�Z�f�p��G�W�<(�g:�����������$-.`�j
�KjT
~V�TM��E�wu��q)6\�^8�mK��{<+10Gn�;9��E8^j.�+a^����Y\65����xK�dA�5��Gt�������72�����G����"���<t���l��8	b&:i�0Q�q���)_��=J�G����"k��c7|�]�E�t�KB���M�#vh
�l'��}�Kz�O��6U�z�:��Z�����^���$����<f�I|���dp�	#&���y^�:�s��wzK^��_�-1W) D
�8`���_V8EO�b'���0��5R��n�T�7��>���4'�[�"�j��I^��XFTAt!�B�~����!R���]�r6��$�|c~�H���5y��+�}g^ K�W���8��5��Q���RS���8�3��x�$4
"��7*u�QMz�S��E�3���0��R�'_$2�������x���rk���tSFS�x�������@�U����@U*5�v�\Lz` ��~���<DK<-v:����@�Jx���2c�d�������������[��B��<�I�K!P=Y��3&�{�A���H���c�'�%Z3RE�K�j}����,s�l����_n$���RB+�������\��`/���T��+t�}�t�D�T+��RM�!�ixHgPV���T�N�p9s����n�w�� 8�������|4qn�@)��IDTW�.f�qxX������E���3W���h� D�����<jJ�bsc�"xd�B��!�aH����4�������%y[�|��>��s�K/5?��Q?��E���7rcI�X�&P��G2{o<�J�*�d���%C7��k��v=�=���e=�h���y�����YGA��������1M��O�z����6�����p�}���nN��4������n6�k2�����swj�D/�f���r4X��K���
5����"�U�H6��Y�����7���R	%���B*�����IV����<���\{B)/f�����B�
(Y(E.v3��*���%�$������L���t���*�G�����{Aq�\�G	\�Wa�#���	�M-�M�l��==����Z�s��H�������F�7K6|�\��.:��(kb�M�Q��h����M��I��`�x���;��l��'�h��d(oJt��`���DO'���(�32��vC&����mM60�N���HFt���Q����jOB�K�xVS��d�O����������]87Q�q�u����$��/��:o����|i�����g�5h�%�������rK�x!��Q@����f�.DI8�/*NBN��t����Y����e�7 5�b��e;��V�����7d���C%Y���N�#��(������>=V����m����=z��tM��qR���D�r���&�������'���][�;�/&��F>��4@A��$�p���Q���4	d�r_�f�	�X[)�QR)%s���T���/��k�����!3��V��|s.)����vB��C���3�������qR�k�06�k�����Ax���X�{�wu�9����@�/�[a��h��J�)�J_V��LA�f�L�'�����uc��jO�R�;������hn�K�gy����up�w{�����
�L��3���z�p�;a����$�Z�}����0[�d�U��	�9dF��J�iW�*��B�Xs~���V��h*��j���%y)�7�t��}>��/�`�_�^�[R1!g�r�MQ�?&��-C�]w"���
�
=x��������@�K�<�3TH��qj@QC��>�i�>���D	���n����.�0��L<�����PJ�CX�!���X'�%7���f��
�
��W��&i�Z���1�i"4���'?��Y{L��p.@#�c����m���Q����s�
"t"�a/��U���M(�����j�����t���lW��X���X�d������*��
��0
�s4�K���E�|��wz����{�a��P��%�������K�2T
�O@'jf�6�j�j���8x�	�.a�$��:�%��	�<R9(KUL�Sm�>���A[����2�t*x���V%�^�4Z�7c9��	�Q�o�E�ND^����T���w.���
J@*u�"�He�Z������\'m2�^��1��&�������~������I0��0����K�n�B��.��#o��l����_��0�I����i_o�w��B�I�S����)6�[�)m���Y��a������L\`D5~������c��)&u�[�
qi�������e����/�o�og���u�3������/�>�Vq+��d^c���8,�/�]���}�o���A�u���8�\�"c�����'��L%�V�����)�TQ�?l��`y�e����KO\�^���r(�������.`O9}rfce^wV�UC�� o��_��HP�a=�`�������N+�c��!�+�o�Nml�K.�M!�N�J�����zh`���|��|u��M�8�Q�{�>�h����������-��"���m����e�������a���������Sj�)h���`,���v8c�F>�����*�&�������s����A)���(�V�T�g"���a����-i_�b5�; /m������;�/�;��X^���KoI����;�ZO��ht�Q�k��7���������-Xs�����H��p��]����]����a���K������.���
�����N0p�#�4��*��Z��Z�yLV r�LV����1��j��q�@vI�)d'�Y}�\����>����$<v"AG��������k`�I)����l�R��w�L�<h�h8��+J&B4o0/c&�^.������xo$.h�]neE���%FWr��cuH.z���ch�!� �7����&{p�m�`�>��:�<��%
%�YO.�%�=Lg�-�y�����a0���x�����'US�$K4������/�2.�0�r �Lc+���;(t���3�B������X��]}���K������*����?�����@�b�e���,���'BYB��N��<��_I[K�"��I��_�2�U���5�����N:��������
�Z�9�f��za��]e�C}��m�xUow��������S�&���,�.�N�2���9t-�lQ=���jg�����.�\��g[B�;v���7��&����b�%���<\ v����.G�K��M�W��f�@+���,��kED�d��/z�?�rV������D�KhB���4�)w���t���ts��h�+�*v��g�`�j��$�
LC��=8�QV�tO� [��aj���J�����h�v�6+/(6��d%���"���*�6�Tf��o�v,����6������y� �;�{S4�u$��~}�
�
�DjOc�i�(�_�f������HX�����D��}8D��S=��,�a!�P��S%@����F�WO����qz�dy�;��*J�N��b�S�@��$���)*1I�k�������'�R�j�O�� R� oe�=
KJ���T����q���C�����������\��Ll�������66����M�����:D����X��_�y���R-����f�F�6`�j(��,��w���6�)�����r��=Kd��a}"8ku��fL�G22�N�X"��=��.�^���wg���Du=a[Jf�����d�����pR)^\��A�Y�H�gy�?�����}M����N�$����������[�
��2I�N����]���������ZZ�I^$$g���+LQ�=�}f�m\@k_�2Y����Hl�O$2:�1{A�;
����g�[EF���Z��'��Y��q���z�R$iF�F��Q.��7��
�F���"aJ���r�,"�ARt�YC<�%�24�����'����F����,w���������S��y5����w�E�<D�f5�������!�p�!�#��pE�u�����/�
����@2'�D��C�K$�6�=�Y�X�e�q�S�����-�0ZF"~9�����I�Ty����o(WeGp�-au���	w�bdk{j_���"���_,�`4��]��>����������P�w�/�A��I��E�j��0�'>'R���5��F��"�S({e�A!lb
�!���i��K�������n�p*����U�J����1��I�[&����5���j}����)
�8�UG��R�hVV�R���05�*o�oG� �(<�e�?g2��1����:����F7i��gl���j�.!���;)l��@ G�Y&TN�w5�����X���V���V�aWZ�r����R��{�)/�Z�\��#���]���"�	#i��xx��v���^%Mc��T������Q�`z<����#uG2*�F	�hl	ME��k'�����O
���3��v3dl+"Y��X��'
G��TJ��OQQ
������������KR
������g�4�M�^Q��	��M�9=������&P�v7^��b��"Q�K2����s�rF.�%@��t�������m��Z��B�C�%*���);*��
���9���$��|�<�����MJ}9+����-��@��`�K��������{��u!0p��xSg��AB����I�
����d��
�	����M�)���G��o��Q~���*�?������������K��_l�7�.l�&�Q-�2�_�~�x,��_�.�6|d�s_�<�?{Fb��%�e��j�
kU�6�����#mt�,%�E|a��l;|������C���t��X�@�����O���	>Zw���y2����$L,�v�\�f0�������*��T���VcSsXU[������h65kEl�����k���`��<LjT����&�$F,*kH�K���\,�`��J��?,���j	I�=d�E1��.U��E��4�������&�h������	����I}���������
.F��K>�8LU���z����(�:��"+�� 5�/U?1p
���������R�G�k5)~�g�
����-+BW-���V�}���[+������}���!z���v����.t�

��J�)K�mV���I�{J6�o�}�c,Y��4r[9@����!�����e���2��6#�^n��'�7���V.���EP>�,��)rD�<��O�����x*���L	1�:%6v����0���+,��T��#YN�qV�6w��hE&;S��d��d�6��+[���v��m���1��f���8�2#�&�h�����|0H1�Q�t������(S��Nc�;0������9v�����h���l��4����_�
E1�g%���-�������k���M
��j!���k`����:������q���V*6��.�/W���1���Ou��w�X��>�=���(��#sLLlE�F���K!�Ry�y����j%6�e�^�g"���p��h	�[��V������	�ip�C��Hy>�W�V�,@�qA�^��1��v�X�N ����pK�I%�a�,(&��z=�f	k����h������7F���(��l[�36�����R�����������9a��Z�C�u�X��B��
1V"�j!1���8�m^W�O�Y&b���Zv�R��~�I[�4YJ�2 B�������V�������B�����,���u��T�}������?���Mg�OD������a%Z�d���1j��*O6��\���W���V�r��	�*�j�+8�5R�Ga���y��0�~xo��;�:��- �F����Co4�:����r�����U*+6y]X��6��:H��R��_[kC��T��;�����XQ��Z$��0�d�e�I~�1���6�'�9v�kzDq")�#	���m�E��"�������R�Zo���� �o���X�X8��5tg���R�T��x����S;��Eh��X?t��Z�=��]����y�c
[s�}��������z������m��!/R�vxDo����gc4L�����N������_�+�g�C����y��Dl+h���~�^ysJ@��8���*=�����{0��(���l�������v��O�Rq+�Q0�=��/<���q���,QE�]��H���j�yT����h����5��F�
F�XB ��q�3p��oewY�x�bN<_3���ZHbN���4))�;�m���e���(��(����������=����sd`����������?D����E�k?��e�.��.�`�����(!�/���6���SDn�^\�k���k�p���ou�t��6�\
��$�l��W�5_�2��8��8d4�����l(8ow��n�E�����{��!K�;������7���r����w�/"�Q9�S��t�c�}��� ������f�z�-8�����Oan�d��b�|,�R9�a���fP>>3���6�9�Ja�]�I����x��w���A_#�R�.9���p��Y�d����T_eiCvo��| ��s��.�|����L
����=�����{8c�?��H�6�;��468����>�����6C�r�SZ��{Lk��;�2���[jq�(���a�a�����H���|@%�WQE�=�eB�{��y�MC�U�t�VU]�(A�o@��K�%G-�<����	���l ����X����Z=�5>4i�����7k��T�V�H���t$�������k�
�%%�p}%���-����{��6g|�.�=���I��kfr��H�PVc�e�P���W�f��������I����b�Z��I�=����c'�0���y��������F�d����S��x��@C�2>�����w8I��R�{p?�)�</�C1z�f	hG��y��+����O�]S{Z������<�R�!R��=��'�>���L�)���6Q[��tg��R@�K4���a/��r,�F�,7��c�Q�1q-��T[����Q@�T
CMYC��e�����f�2:���dzb��P���l�nrY-����(Pc��eFdU�&L��n\6h��z���-�'��-Nu������w��j�/�r��<�d�fIF$=Hm�x['FN�E����5����J���n�,2%�[�<�x,�+�
��?^R����S���4� ���H`�A���>��e>�d�����c�-���,��}c��(��)Kk���������������������	x�#�>g�������������k�����[�[��w?�wD#��2a�(}4���D��q�33��f���A;[
�kv*g�����q/|�s`��8����Z�A���}��	����:�v834�@y��$��v�6��<s�A�������u�3P�'��*`,'�H;��3K�(a��R������L�e?o�����n��t9��`k�+��
E�����P��F��w�fj�	�����������{o:�	��(r�("�z�X"B����wO���p<9��a�]9�C���}
"	�K
�raM��qm��9�,��`�3z�+Mg8��;o����C���e^�����5���*bg9���yC����A/����' Kmk�J�b��g�^�������*p&n�%V��n$�3���0�S�]��Q,����<k�&���i����]-�e�}�.�w�����Pf��@ChT]0�+Q�*�&0�'�l&�L��[������V��xKl}Fkl��Yd3!��p����}-K���������4�2nb�4Jb�W92�[@���A�0����bl<��ND)�)���G�_�0�3 ���3�V�>�m���z�����8���0�W&�=�\c2�#fg��s���P���g���)y�*|ba�V2�MTV���3�h"S�|h��(%)\1��I������o�����j��Zq��#�b8�r,�B���{�������Yr_�N&����|�D�1#T��*P$������W���%?���~����S����Ba[���RW�� ~_f6n�1MO�6�w_�z�y�9��;��j��G���g����kr$^�W�w������
��.��xy�{�yr���>�[������?�2�����ILJ��rNJ�6K��m	k?�$
��������SK-'��^2-I��
0��IZ�y(�U�\�oB�`gG��?�\P�H��!t��w�B@������u��L5��6���s�}Y6v���^(�dh����7'�����K�����N��*^R�3J�j�+���N����Q����*[O��r�~�I���r��X����B&l���F-�����"5����������_�opJ��������M"��(�>�gd�J�\f	�\L�Kl(n�O�)��	������W��j�2)��J%
%�Cf��En5�R����s���Un&����	���>(�m#�V�A�j����V�C}�w����#`���v�t(�WRcz�]�6�^���I��u��b��C�r-���z�\����-�J�zf���r�Nx��X�L�UW
3�W�?�7)�Km�����mN%'[�R�����36�rmCB��Vn�0fGvk����N+�yD�q������~��	~��(����^1�����jRn�����pS���K:����K�,�L�&��P�UJ%(AA=����e����'d����^,�����J|��$~�
]�2S��+S8_0���X�6v�Y�w�0��m���7\�{���f����e=�sF�V��!�pRl���������$��&_��R���zI�Lw�#Q��gc�x6Qc�cLm}��������aAdY����?�q�s<���6�����kR\����P�0���Ay��K�.���x�:�$7	���v���#SGo0�� ���X��a������r"w���zH�[����3��	q�*���q9�*O���c����7�uq��Y����&vgD@�%�[U{k�r������
f^�j��.1kT�L2����_#�Q�Fk�$��Teo����g������<��P��8�
e�x<�D��>�n���G��#L~�?8���?��{4������������d{D#+���8h��|�2�����m��s���{���r���
�
���_���O���w�E6��Y�z���`�����gL��������:�gZQ�!��U�v�Z��a_YU!+j�v��_k,�����\qq����c���0�T8M������mm�R��F���UQ*W��|���c��"��*
I��;&�)RC���M��t���.�]��>��k*����G����U�{r�R.&�\�y������'�]���>[�T4�$����M�'�K�I�.-����=���G���C�
�e�~����P��"�������1�a.0�V~���������:�����aK��s���.Ee����loW*4����j�>���J]rn8�"/��8*������H�5J����n��@��R�t�*b��,�:TQ�����q��`G�WW�)�|b�qj����0*����3�w�2����
g ��,��0�{�uJP-�U�qO�{���%5��V^��������nJ3����GG�2';1L�^U�F�\���
��u&������_a)V*��J��
���z����Bh$���~
.�Glr,6Z(\
�M�� ?��v��u�e�>=_A��1���H+r��]F����>��1�?Zur}��{����!�y��������?j�f�}ny�?��?�o��<R"��V{2��F]�m�mK����&
\���2����v��h�R�UYT��@7�3��)o= �wV�1W�)&f�&�f�?��lo+X��Vl���=	y�_:��,p����B���d#o,��f�5e�'�4N���;��55�����0����Vi/���(aX�[���m�L���u������x�����W� BWW�82�f�?4�sj�y��wX�:��	��D��T�J)��9��r:@9�()��#7eI��><����>�F`�E?�*8�2����4��:f�jG&z��'B"��������p�Z8�x=U�=BU����,�0�|��oVP�	?�q3C�2��Q��C���g��u�������UV���R�oX~�>=���A�����y������K������g�������������)�+��#�F��+
���FR5+V$��qt��{�
�AJ)#�N���t!.-;��7�X�x��?8�oI�'��p�4�E�_��Q#�>Sq�8F�)jE�BPR�WE���Je}9�(�h,Q/nh������N��P�N�����@�'��R��b=�+pU�WZ+L��M8W��{0J�(1�Gk'������1�cYAO	0�2����%�nw���i\�f�f��';��Q�����:>,Kq^Xd��ax{�59����h��
@���_�N}�]"�G|��r\F���>�#n�[v�X�$����c�A�e^��wC�a�nL��j��&�j�+0f�Vf�����i�;X�4�?����0�Z�4(�b����"���}FyM$�	�����/��8�A�i�C�;���9��**��x3
z��L��`���O����;�D)(�hS���s�w�t�
�Id��M.�Z&^<�(��hi�/���9�x��G1�3�>���\*��r�T����f�
Mx�����0��#������m�l��M2 c33�R���\�?�<bv���i\�J�b�GK�V����G<�B���)��@��bl�9G����-�vjQf��������Sp1��i�8����A��jP��R�%�1�Z�@�aHq�a�^��P��zRA�q����! �K��}%%U����}<0�������T����E���7���,s$��l~^Fq|(/4�������E>J~p�!�����g�Y����N����<J]X�S�������?� �������q#��~0�@�X
��
��/		��k��K1��� �01��pMY��/T�nC�2l�z��r��2����O�:(�==�S�rK+�$�wky���R&S����^G$3W��v5p3�sk��o��I�2�c�XE��P�h	�K
y�[D���_ _I7�|�	%<�����!� q�������bU�:J>���)5J��4���c���k&~�b���{Z"�:��K�X6.�p�I�s��j�^)�rTZI��/zj���>�p��_B&m�?�!v��F�������wa<�/n�I����cN��X��^g�+��s^�!�����d��L�o}�tP������{�2e?���������F�����f�[o�7��{l g�(i.d�&�h�-��(��<��ri�j7\m?�&����z��,��8L�u�e*��H<z�k���.�S��qe�E�,m�����C��-,��^pz��}�q��Z�a�p����b�2;�������`�W����p����J�����R��<��&���51E��+u�h�JZ���a��m��o�cw��)t3�Z���X�����������<�5km��������n�D~�A#��q�����V���dS7���K�0ai�R��US�&��/[����i�G��HZG#?IR�}%]�m�T�\�V�>�����������dW�?�.&*5G�&�����|��j�xa�v�?:+F^8���
�*��b��������^`�}D��>0p��W+��zqk��1���������k<�}xL���� ����,@T���*'���>���n�>f#K�%?B?���Gi�f'�������[�	������2���jv�������l@��~��+��o�~��}#���Cx?�}����wC��7 ����>����_Y*���"�^������4J����-V[�h�����C���E�����w��\\w2�FF���&����#���"6vSm@#���v_�{�g���'�������0��y�u�XRq��4�_Q�&���\����`���)[��u��a��z��C=�b�@d��������-�r|m9�Z�C��+��X�����>~8�':��~�=}=:��X�=����s�������a��nV�v�&�fd&G��DWLq�dm5�_`q�����v�zo�tN����
�v����vU~+�v�i��������v�#.����3�Xh�Ao���v�������;����c�����xv�����W��������6��{:����Iw0�P��^���}����Rc��a�����9����cf1t_S�~G"|=��:�����w��wr���=������^��5������������J������������F8?
<��O%4�O������o�G��o���yg�+�����N��k6jv��]����bJ��V���[�����c�'(��~C����O�`�x~@���_�;����fm���?N�v��C������~�m����'w�{CX���@"k��f(��~���\�����Mu���A?P�{�u���������^���
����i��b(<���y����� j���Y[���X�N��-/=����?���t>�g��z3�o��3gW;��d��V�f��XP���f:����4m�R�U���y�-�a�0,E����:�{�4d��c����g��
��13�L�9�:��g��"po*���0���T���_�4��/sq��
jq%����s�S�e�:�*��;xiX���	(����G�?��~!����@�e=2T` R�s'.�K��6�T��I/��{�T�E����o+��2OtZ�K14W�Tu;���	���pj2+�H��gjK�2{���"��}k�c�����#~�`)�$H��|]	i��C`���2�)��FA�s?���$�nFd�d��=X��������>��w>�%#�"clEE���x:8�,��~�����������e8���%�r���De�TN�cW('.c#���!Z�I�������ez9����2���	D��~n�����l5�u@6M��#������� ��b5J�D��_����1����5FZp�r��t��Yhr�H1��<x|W%�V����A��FhL<A����r�^�?��z����}�^��2$G�\��C��W������.�q35Ku\��=x�f�MhiDv�F�l1�fh*��s�����Gc��Kcd�3�R��1�
�����t2�
@��"{*��	����������jwD��!����.����������0<�I�����i�<;�>VI��� ��eOG���U?oa4��6�2�-���{��+�%>����/���]�.+k�@��sK�m��X��
i��IN�c�H�q3���q�j�0T�����P�o�K�z]�,�G��j���G���y��?U:�<��lu#Q��#�Q�]x�r���&Z	2���@�y��Gf����'���Ol��_TH��{�5�t�T��A��"�2�=���w�gL�	�Js�`]?���3��i������N��)��}���t���T�&v�q�T�u�\�+C�8����:a�$���@���a�d�pw����P�����sj������[�G���.{�N�ZN�Z�N��	�&�����a����a��e�ZI���<�]S����J�dP�������&���;�,�kD�F��F�>\GwOS�ldMB^���3y�$8����������	���,�2�[O��]|,b~o:�����T".�����
�
5>�m�8��U3�m��W��j�an��0�����O�a��F0y##-hV���}0��D�wB�;���������4Xy9�F|#2�������$����F�?G�B��H���T����������f��D���r^wSohVh�L�����[
��������6���9�f�A��0i���@���"]�X/��Q����f�����	*�J&\���yl������
l�a���Wx"���Z����O��g��g��!L��m4�����R�R�������x��3�)���i��J	�KS�<8|�A��T8�>��\����|@.5��b)�89���+J�����t����f;\�r`�+��al��v�,Lh~h0��-}a`RH�L�w�f`�Rg��es�X�V]��I.�J���x��G
X�JbU��>�������|��s��v�����s?3����;/
��;+6G������3��qW4��;������|i���y��5��������-��[_�s�7���.� �\jA`Q�\��&q�^�X���]�v��X�|���3�n��9���"a5���2#f�
�+���n��G�z�V���g?	��
J����_���E��i�c�	J/�Pl2I,O~z/��;
�G���pB�A!d�����~������!��fhV`=[�M��/b}��,�a[��q�*MX���w��*xB0u���IH���d�kc���;/��=��Xn�l���D��J��kF&G�g���c|���d�Q�;��r�aW��J���_c�f��2o�Z6g�&6��-�M3�����)�$�ts�flW�<K�<�{��!�r��-1��k9Td}~�0��"��!&G�Xm��������T�/{z�:��k-liN�4�vC�
 ��)V�FhE,C�%B!�@i)���.g��G�]��3�0����0R>��]�/b�bnZVQ�Z�*��X��j����61)�1��Dc|��BD�[	l��	����G�a���� k�%b�z�]w����K����~@e���=.4+\B`���Q�G����YI`V2��s�L�a���^;�=J��2�����X�������T9\^����'n������4xl����_�ipQ�Vo>�a`=�
��~����'p)���)eDc�����{6g��B�V^:di���97�7�4T�^S�?g*�Bd�O`����������dD��l*x���Z��%f�����v��r7.�x��Rt9��Z,�������\b�<��t0�V���	>�Z�!f�p�8�.��e�����-�9�=���1����K`S����5��3liG�4[N#�w(&jc�/����������`:� ��~���>�U�["��d�\u��`v�p$������YY}�����J����N�(
UEg�	E���|�;�
���������r8h������]*���������oZ�R�i����6+����q0������N�����`;���LI�������b�@���b�x�K�XU�_P�p��|3���� ^�j��s4R��+T�Kut�0�n���X	�x)V�Y,Es��h�k)��K#��nI�8��ne�	��������_f�{�at���	�.�����x���1��F�ezD�o�AdcaR�j�*����������Q�:{}e���]��V`U�GD?��d����t$�p������_�EE@N�-�?����XF�Cm��%���c����9H4�������&a �	1���4�m�9���h�E�}{�Q<��gwF���C`�5��Z�X�g��x�l������*�~��������I����GB�T���(��
���o�Pi��P������2��p���7 ����2�����n��"�
v���f@���a���=���0N�i�t��x��������U������%�^���}�/\ s�,�2\����)K��[���q�[v��"{�]�[4f�Fc�t������@$%����On�\�	~D�q��� ^������?�:x�{�O_K�p�e2Ta�a~�,t����������+��cZ��p��8P����M:�������,����i���o{V�8���S��\Y�\ �������W1.d��`X���K��*u�_D0��x@?�'B�)j1;4�q,��)U>��I��R�Po��G���n�>���%�\��H4=��M�Z�`�������sw��Y��p�����Y7:J�N����[������q^S^}�g?����Q�Jv�^3b�AF|�U�����)����-����F������N������Q�m�}[
��P�G>� $V������p^�#qW���*����j���R����U��?�^O����7x���Y�:���uMUm��^��,r��s\W`In���0���h#[sU�a�I��w9�[��hF��O����vp��q���~_;����w/7�I�����w8;��$��8������������!>�B
�|�]Z$�PZ�`6�M�pXz1�6��Z-{:]��w�z���;?���'	GA�q�\=E��"-"��O{�e5��Mv��� K������Ia���J7�
���+�!	K
�K�p.�
���	����,e!��:=�^�G���3/�;,��=����L���R��_�o�����������tB�(��2�&ZbZS�?a�t��_��'���t���p�����m���l���t7:zs<X���N�����)i'��C�kKzLt�����"���xX��O���������o .*em���:vM@ZE�N��:��&�T�%�R�'%��|9�����
T*�;6��0�Lh�??Vb����
<���������"K�m���O1���JXg���c
���u�;����)�j���3E��X��q���-���a,�b�
/��y��f���p�=���^U�x�Fi�8�����"�QYLc�\��E	��V]W�gd���������IjC`-'$����d�*,������
[��)9C��<w:��������a�*��Yi<3o�R�f�J���z��w�Db����
;��(���=����g*�a�x,V�d����=$q����W�7���rRZW1PYU*K��v�1����V���m�e�(��a"� G��N�12�qWY5����0�5��hIM�]@u�O`-g�����Ok�DB�������3�
���CN�6 ������w���d�C
/����A�������'rx�<J�!��1�Z�������cb�����g)�ra4Qyu��8,'�����3'�"	7D��%R{�^Fd����oN([���<���"5D�z��"9�@�z�h�-u����������$(
�����tsp.�wO��US�E��3����;��L$�?�"�{;a������3Q�x��J�D�"&!%U�3���v��cg'?t�@�#���K��g((C��R�O�e��V�p�	1;B�{������O���������$��Z��X��R���(����G�,�(��x��>J ��":u�.�S�d85�c��&��-K�A�<0y�jL��b�
��t�})2�'�(�
��$H\Q��t�V�C�2�n����8a�����X��ru�7[��ieLH�,��z>Gp"�$(J��fe1
=�F�f���I�	F�s]�����&<���X�<�����l%7��v�1���*����Ze�,[�)�o���6]�-_�$��=�C��'|�L����h0a���Ny����I�k�x3� �@����@cq�K��H~~�=�����e��=�K)9����O�|14}h
��E
��{���F�O�0�J��b%�^�����C�\���c��]����0`�:?������cYPD��V�B.��;n������d���XuD���*E�k�2<��\a?]A�S��B�����?K���H�J7����U���!@����{��/��V�%�Q�]��#���;G�v���_�J�\SM��,�����n2����*�6,,���N��
�~_��sS\y��z�3�*�Ct�JS�{Q-��o7\]������0;o������s�>���-f$�"&aab��E������(�c��~ �R����x(���(,��|e�N����D=���}w���H�����X�E������&Kgj����qH���d��^./�0�P��9�zs���P���f��������v��
l�5����[�q���F �PQ@jP\-�����������b�X
���;����������v������'����4�h�E����Z����%d�z��Z��@��Xf�-�����_*���/P���-�Z#s�,��n
�D��|\����VN��H�-��
��3���RYO�J��TA�FVF������~����($4B�>���G(4�-b����T���I�\R>��`���H~1��/�����'9�5��1���1�L�G������N�����T���H<`F����T��^w����hX��r�gY|�<d�?��)����6	���q�	�6M���w��^�]�3<��_W^X��<1��M��mX+a����W>l��?w�������(���V�zP[@�������W�O�����j��RK��Xz��(���.6����
����\��Q� �/w��P�����]U}��cw�c����Q8��D�N���"���f����l���m�[�U�_-�h,������[��y��"�!Dcd�3��/��\�k�O�	{�;��r�n�������������=a�����a�-����'��H
}C�g�j����
���1�_�oX���R����\-�k�*��]��.�H��<N�6,����t�h��&1>�i�^�Mf��8r'�H�_F��I�&V[�6~�A;�Y+����.���������]J2�'�hT�a/�6.�����*��6\N{C�Y���o�����:�*5s�B��-gYp����)����A�/���E��*��B�������{6?(���n�AM>��
���
J%������;0t��(��X�<N��}�f�,`����L&B}������+����u��Z��.\�
0�=|��#�t_�{�g���,�����7������J������fe�_����=(����V
*p�k�t���~P���O��}����o�����vn< �������]A�x�4�gD�Y���F�]��G�y�O�}���C�X����_h��_��b�G^G��	�W.���.	�K?�x�X�D�hx�BW�����j��7u��%��l^{�@�+pc�rvc,�z��c�t#h"	���P���0TC�/e��/�}Q���7�-�X^L��!�A�i��=<a�Q{L+���x����}��c��:Q(���m�8��	;F5
��o�����O���!�T1.Z�%7���T���2���LKV�9p�����Q�l�I:&��=Io�.����a��@�z���3�*�4wZ�8ru�.����~}�.��A�`�5�oe�	�������vp����<��r��g�����w	;.�r�bb���a�B]2w��I��bS���\".����M^�L�����z��N_��6:	�����1yf��O4�u,���7F��c��\�<�i�V�:���k����L���	���rGT������kC�Pn=��-aF��[-=��7�!��
�����x.���1�Y�>�]���{!R����WV���`��%�����N�%k	��d�p�{���[�z���r ���y��o�t����*�x!�E�2F�\q��z/fye��2/?��s���������	��sc#���������������v[f���]��e>������K#��d�^o��v�`�L�����6v��:XJJa��d��.	l���Q���D:�v�Z���n���R��XJ�L���mg���-{�55K4fn��6��
-��bG^���!�G��P3�\��,��h��E��=8jwPx���8!P)R���a�2���!�5�R�%�K�,���	�������$j���)C>���].^��2���:n���;3�lXr�r3^������)��%��ZlcO��_%S7#,[��\��������b3�������!N��>9I�d
bxC-��P���~+����?�]{�X|6���
;y�&�=K
�������Yq��\���;d����=H�O�3���d]��M[�F�r���S�a�af+0f�TgvG�h��-�+�okl�����i�+�D��f�WN#��V��(:�4m�3���Om
��5��'��)d:u���a���yj�lA�����[�@j?�������//�M�C�N�CG�E�R�?�s�F��P��s>W�U��f^8���]�sN�	���0��
�s��`�L��E%�,'��x`V��B��^C���~M4Q5�5���L$'B����.f��V�.�:B$��m����s��b��A�~$�x_Q���(��a$�������a�[�M������--�*��-�
-��Bm-R��ZB��:���b;�?���R���f_S$+��W����Sh�6P�|����:[+��*
3�}:��6�\/J����(Q�K(+�&�H����/��,����Z���}bY�����B5�W�
#21Thom Brown
thom@linux.com
In reply to: Kevin Grittner (#20)

On 25 January 2013 06:00, Kevin Grittner <kgrittn@mail.com> wrote:

Noah Misch wrote:

The patch conflicts with git master; I tested against master@{2013-01-20}.

New patch rebased, fixes issues raised by Thom Brown, and addresses
some of your points.

Thanks for the new version. All previous issues I raised have been resolved.

I have an inconsistency to note between VIEWs and MATERIALIZED VIEWs:

CREATE VIEW v_test8 (meow, "?column?") AS SELECT 1 bark, 2;
CREATE MATERIALIZED VIEW mv_test8 (meow, "?column?") AS SELECT 1 bark, 2;

pg_dump output:

CREATE VIEW v_test8 AS
SELECT 1 AS meow, 2;

CREATE MATERIALIZED VIEW mv_test8 (
meow,
"?column?"
) AS
SELECT 1 AS meow, 2
WITH NO DATA;

The materialized view adds in column name parameters, whereas the
standard view doesn't. But it seems to add column parameters
regardless:

CREATE VIEW v_test9 AS SELECT 1;
CREATE MATERIALIZED VIEW v_test9 AS SELECT 1;

CREATE VIEW v_test9 AS
SELECT 1;

CREATE MATERIALIZED VIEW mv_test9 (
"?column?"
) AS
SELECT 1
WITH NO DATA;

VIEWs never seem to use column parameters, MATERIALIZED VIEWs always
appear to use them.

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#20)

On Fri, Jan 25, 2013 at 01:00:59AM -0500, Kevin Grittner wrote:

Noah Misch wrote:

*** 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.

This smells like a bypass of mandatory access control. Unless you've
determined that this is correct within the sepgsql security model, I suggest
starting with a draconian policy, like simply crippling MVs. Even if you have
determined that, separating out the nontrivial sepgsql support might be good.
The set of ideal reviewers is quite different.

Robert suggested this way of coping for now. Will post just the
sepgsql separately to try to attract the right crowd to confirm.

Sounds good.

Let's not support OIDs on MVs. They'll be regenerated on every refresh.

Do they have any value for people who might want to use cursors?

Not that I have heard, for whatever that's worth.

+ /*
+ * Kludge here to allow refresh of a materialized view which is invalid
+ * (that is, it was created WITH NO DATA or was TRUNCATED). We flag the
+ * first two RangeTblEntry list elements, which were added to the front
+ * of the rewritten Query to keep the rules system happy, with the
+ * isResultRel flag to indicate that it is OK if they are flagged as
+ * invalid.
+ */
+ rtable = dataQuery->rtable;
+ ((RangeTblEntry *) linitial(rtable))->isResultRel = true;
+ ((RangeTblEntry *) lsecond(rtable))->isResultRel = true;

Is it safe to assume that the first two RTEs are the correct ones to flag?

I'm trying to play along with UpdateRangeTableOfViewParse() in
view.c. See the comment in front of that function for details.

Ah. Perhaps assert that those RTEs have the aliases "old" and "new"?

+ ExecCheckRelationsValid(rangeTable);

I believe this ought to happen after the executor lock acquisitions, perhaps
right in ExecOpenScanRelation(). Since you'll then have an open Relation,
RelationIsFlaggedAsValid() can use the relcache.

That would break MVs entirely. This probably deserves more
comments. It's a little fragile, but was the best way I found to
handle things. An MV has a rule associated with it, just like a
"regular" view, which is parse-analyzed but not rewritten or
planned. We need to allow the rewrite and planning for statements
which populate the view, but suppress expansion of the rule for
simple references. It is OK for an MV to be invalid if it is being
populated, but not if it is being referenced. Long story short,
this call helps determine which relations will be opened.

If someone can suggest a better alternative, I'll see what I can
do; otherwise I guess I should add comments around the key places.

I see. It seems wrong to check MV validity before the executor locks a table;
if the executor lock were in fact the first lock on the relation, then the
view could become invalid again before we lock it. I don't know a way to
actually make the executor's lock be the first lock; some parser or planner
lock invariably seems to precede it. Is that proper to rely on?

+ /* Strip off the trailing semicolon so that other things may follow. */
+ appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);

I suggest verifying that the last character is indeed a semicolon.

How about if I have it exit_horribly if the semicolon added 21
lines up has disappeared? Or use Assert if we have that for the
frontend now?

The code 21 lines back is adding a semicolon to a query being sent to the
server to retrieve the view's definition. Here you're removing the trailing
semicolon from a column of the server's response. Granted, there's not much
reason we'd ever change the server to omit the trailing semicolon, and the
breakage would be relatively obvious even without an explicit test here.

You have chosen to make pg_dump preserve the valid-or-invalid state of each
MV. That seems reasonable, though I'm slightly concerned about the case of a
dump taken from a standby.

I'm not clear on the problem. Could you explain?

Currently none. If you took my suggestion regarding relisvalid, then MVs
would be considered invalid on the standby. That is one disadvantage of the
suggestion, perhaps.

Some of the ALTER TABLE variants would make plenty of sense for MVs:

??ALTER [ COLUMN ] column_name SET STATISTICS integer
??ALTER [ COLUMN ] column_name SET ( attribute_option = value [, ... ] )
??ALTER [ COLUMN ] column_name RESET ( attribute_option [, ... ] )
??ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }

It wouldn't be a problem to skip those for the first patch, though.
Conversely, this syntax is accepted:

??ALTER MATERIALIZED VIEW [ IF EXISTS ] name SET ( view_option_name [= view_option_value] [, ... ] )

But there are no available options. The only option accepted for regular
views, security_barrier, is rejected. MVs always have security_barrier
semantics, in any event.

I think those are doc problems, not implementation of the
functionality. Will double-check and fix where needed.

Looks so.

Overall, I recommend auditing all the ALTER TABLE and ALTER VIEW options to
determine which ones make sense for MVs. For each one in the sensible set,
either allow it or add a comment indicating that it could reasonably be
allowed in the future. For each one outside the set, forbid it. Verify that
the documentation, the results of your evaluation, and the actual allowed
operations are all consistent.

I have already tried to do that in the coding, although maybe you
think more comments are needed there? The docs definitely need to
catch up. This part isn't in flux, so I'll fix that part of the
docs in the next day or two.

I won't worry about such comments for now; it looks like you're targeting most
of the reasonable-to-support ALTER operations. I just didn't realize that the
docs were out of date in this respect.

By the way, your mailer omits References: and In-Reply-To: headers, breaking
the thread.

nm

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Peter Eisentraut
peter_e@gmx.net
In reply to: Kevin Grittner (#20)

On 1/25/13 1:00 AM, Kevin Grittner wrote:

New patch rebased, fixes issues raised by Thom Brown, and addresses
some of your points.

This patch doesn't apply anymore, so I just took a superficial look. I
think the intended functionality and the interfaces look pretty good.
Documentation looks complete, tests are there.

I have a couple of notes:

* What you call WITH [NO] DATA, Oracle calls BUILD IMMEDIATE/DEFERRED.
It might be better to use that as well then.

* You use fields named relkind in the parse nodes, but they don't
actually contain relkind values, which is confusing. I'd just name the
field is_matview or something.

* More generally, I wouldn't be so fond of combining the parse handling
of CREATE TABLE AS and CREATE MATERIALIZED VIEW. They are similar, but
then again so are a lot of other things.

* Some of the terminology is inconsistent. A materialized view is
sometimes called valid, populated, or built, with approximately the same
meaning. Personally, I would settle on "built", as per above, but it
should be one term only.

* I find the name of the relisvalid column a bit confusing. Especially
because it only applies to materialized views, and there is already a
meaning of "valid" for indexes. (Recall that indexes are also stored in
pg_class, but they are concerned about indisvalid.) I would name it
something like relmvbuilt.

Btw., half of the patch seems to consist of updating places referring to
relkind. Is something wrong with the meaning of relkind that this sort
of thing is required? Maybe these places should be operating in terms
of features, not accessing relkind directly.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Kevin Grittner
kgrittn@ymail.com
In reply to: Noah Misch (#14)
1 attachment(s)

Noah Misch <noah@leadboat.com> wrote:

Like Kevin, I want a way to distinguish unpopulated MVs from MVs that
genuinely yielded the empty set at last refresh.  I agree that there's no
particular need to store that fact in pg_class, and I would much prefer only
storing it one way in any case.  A user-visible disadvantage of the current
implementation is that relisvalid remains stale until something opens the rel.
That's fine for the system itself, but it can deceive user-initiated catalog
queries.  Imagine a check_postgres action that looks for invalid MVs to
complain about.  It couldn't just scan pg_class; it would need to first do
something that opens every MV.

I suggest the following:

1. Let an invalid MV have a zero-length heap.  Distinguish a valid, empty MV
    by giving it a page with no tuples.  This entails VACUUM[1] not truncating
    MVs below one page and the refresh operation, where necessary, extending
    the relation from zero pages to one.
2. Remove pg_class.relisvalid.
3. Add a bool field to RelationData.  The word "valid" is used in that context
    to refer to the validity of the structure itself, so perhaps call the new
    field rd_scannable.  RelationIsFlaggedAsValid() can become a macro;
    consider changing its name for consistency with the field name.
4. During relcache build, set the field to "RelationGetNumberBlocks(rel) !=> 0"
    for MVs, fixed "true" for everyone else.  Any operation that changes the
    field must, and probably would anyway, instigate a relcache invalidation.
5. Expose a database function, say pg_relation_scannable(), for querying the
    current state of a relation.  This supports user-level monitoring.

Does that seem reasonable?  One semantic difference to keep in mind is that
unlogged MVs will be considered invalid on the standby while valid on the
master.  That's essentially an accurate report, so I won't mind it.

Changed to work pretty much as you suggested.

I'm going to follow this with a review covering a broader range of topics.

Those issues addressed, too.  That includes the most egregious doc
problems you pointed out, but there still needs to be a thorough
review, and I expect to find a few more doc cleanup issues.

There was one minor syntax issue not addressed by Noah, nor much
discussed in general that I didn't want to just unilaterally
choose; but given that nobody seems to care that much I will put
forward a proposal and do it that way tomorrow if nobody objects.
Before this patch tables were the only things subject to
truncation, but now materialized views can also be truncated.  So
far we have been treating TABLE as a noise word in the truncate
command.  I assume we still want to allow tables to be truncated
with or without the word.  The question is what to do about
materialized views, and wheter both can be specified on a single
TRUNCATE statement.  I propose that we allow TABLE or MATERIALIZED
VIEW to be specified, or that part of the statement to be left out.
I propose that either type of object be allowed unless one or the
other is specified and the object to be truncated is not of that
kind.  So you could mix both kinds on one statement, so long as you
didn't specify either kind.

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions.  If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious.  Where it gets  a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated.  Then you dump.  mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable.  As the patch currently stands, you
get an error on the attempt to REFRESH mvb.  I think that's a good
thing, but I'm open to arguments to the contrary.

I don't have any handling in pg_dump for circular references among
materialized views, because I couldn't see how that could happen.
I'm not 100% sure that isn't just a failure of imagination on my
part, though.

The only other comment I know of that hasn't been addressed is
Simon's suggestion that I put in syntax for features which we might
implement in future releases.  I don't want to do that without the
usual community design and bike-shedding process, so syntax is only
implemented for implemented features.

I'm still waiting for final word (and a small patch?) from KaiGai
Kohei for the sepgsql part.

This patch does require an initdb because of a new function.

Unless something else comes up in review or I get feedback to the
contrary I plan to deal with the above-mentioned issues and commit
this within a week or two.

Thanks to Marko Tiikkaja, Robert Haas, Thom Brown, Simon Riggs,
KaiGai Kohei, and Noah Misch for the reviews and suggestions so
far, thanks to Robert for the initial cut at the docs, and big
thanks to Noah for helping me track down an elusive bug.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

matview-v4.patch.gzapplication/x-gzip; name=matview-v4.patch.gzDownload
���Qmatview-v4.patch�=kw�6���_����	-�!�E����j��Ii�7��P$$qC�,A�Q����;�/�d��mvkKf0�`^����W�	|kXq-s���4�P6�������/���;�V�Z�p���~��+5f8�t���MY��`u������,�a�~b���� �ln\4���?:�6��Ct���#�<���!�6q�@yC��O��m�#�X����C�Qb"��P����S'�;���s��$<����Njv�H�b9&"J[����M���?���F 'u��5S�7[������.���or9FR��;	�jdk�8�U-���C�pon0�����k��r��y8I�q/�v["-��G�%d����T�����5/zMS���b���,����OH/2�	&zc_7i�rFn�8��U�r�d_�?9�rL�P5���tf�,�~���_4r>�!����>�6�&�7/�:�F��o�x�$�Q2R���&`_�����>(�-���n�rN���3F�eJ���pP�9M{#Up����+/�
7)�\�,�q}o�;�$�zD����DR*9e�y\M����7*�le7]��(z�:��Q������g������p�%-�L/�9�?��9��7������2Y���)�zo�`!Kh+����?���-q)f�������4bC%��l�^������m�nIy�\���	�=�����F�Ot��k��)�x�<��x#/`�07�d��9��I�3�ec���]��A�����G�c�	�.&��d^��9�42�aF�n[�{�-z���w��a`�f_S���5�N��;�U��{�Cm/�{d��1�������
�4�>�Js��)hR�����/�
-#H���;���`�a��QD��/��s�f��Lg+�}�bl���|�K�?�m�%|�(�x�C
��
��T��lZe�t3�2REm������E����y�L6����������|:-��kj('��������bn��kR!����vp�����_bU�X�$k���klc������oFl���$G~]Qv���/��H��q~�e�=��?+���~J/~�H��bk�}@#�RW~@�K#oC��og�ms����~&��k��c�=���o�o�:�����������g�}>���~��~�E�i�l�n5?��q+�s��CS��k���l��&0�A�;�4������D^�q\�]��?�x���������w(��E�
zhLt�"�jD<�)�������h�7g������HN)���(�y\�D\�),y@��f+e�;��|J[����Bp����������������!W��[`�f�H��=���H�������
��.�W��Re�Pb���������,V������]�{6+�7��tP��������n���g|>�	%�������#{S��F=Y	��=uY pQ�Ng��r��`J����6����� �$�&�������
�,�.Pp�e$:Z�F��	���l`�l��T����g��m�Q�]��L�w�)��X=�x.p%a���k>�adQ����Z�^��>D7��J�Tw�:��#�>eT�B�����S`��yT���j��n@�������� 6�$�"�48�y�<z>b/�T|,q�L��O�3O�*���@���j{��G�}K�,�����=�<�Nj6���������M����`���[���H��|O�G�JA^���O
�4A��]�c �Sb���D/`������z�
���������;N��'�?�T4�S'�������
+8/��&�J�?8�V5�y*<I���4$�h`����������A(3��7�9����W>c/0�\"\�)�U��`�S��|�z�R<�|k
���+Z�8:3t�A��*Q����������"��(�HI��M'G`3NN���X�\��B�E�Em$�"�� ��&�j�g�����xP��n�*1h���_|��HV=���*�c���\O ���&���]���O�&�j��;����$�R�N=�~����)�N
F"~���*�<�`�&_�0p����5�7��a����o���J���n|��
���I��u�J��-<�+���
����l���M&q������J�T�az��	�2����m��}o��V�+r�i1aY������ZX���=nO�<���m��9o�������{��}�VP���������=�K`^������F4�-oT�W#��3Ow1�<����p�t
�irP2/�`B���,�Y6%l�:y�3�����v�� ����J�U�sLn���F��������0��\���1a���0O7���T��dH���Mh��l[>$������}/3�[M=En#�/����DI��gS�&a��g�p}���Z�C������OG��Q���8�N�����/����������HK���a	sC�����[X���a�YY����8�Y{�l�[�_�n��&�3��K���$��I��9�������C}LU��l+������
Xh��y�r�2��?��fQ�5���"::h
�I5W�-�=�'6�ay�e�[}j����?��U�qt����nU����n>&������
�4c�b��9�Vs�Ej%�3z59�W/��C"XE3D��j�aY�Y��n��E�����z�z���k{���� {�Umo/�"x'� 	���PA�1��d�����Nn�r`{�jp�U;fw�,�j�J1Z�iH���+(����W��/�88l�9���C���+���-}_w����<� H�`Y0f���
a��5
\lS�Xb��I�j������VV��t�4xb@#����s����������������N�=48X+rX��b�E�W�k�Uq��4.��.i���l�Z�68�����������U���lB���H�����y�s�������0��v����69o��c�quC.H8�xdp�KwE��}�_a��7��T"��*�C�Y�	����~^	P1����3����r�������_6`~��E����(��Q�����,��4��\k�Q0�ON[6�o�'*�m�1�g�^o}���k��-��s�e��J���y��l]��-����b����K� �^�����a���#�7������/�����O{�/4�B��hh���E{`�����2�lB��	��.n�xg`�.B�4����<s�Y��&@��s��N8��*����b�o��!��g)N�q�q7N]%E^D.J�����4�"Zy������h��	TD�o�p��a�*[��)KQ�������C�l���.PD�))��J�>��8��S`3��������;~)�{p�-�V��+�X���P�b8�")�c��N����b�w>�o��t^�O��M��(�6�0xii����H���}�;��y�X�4�}T�J��D��+rY>��
�XKc��.�~P����I�M������o&�u�-��u0��
6%�,>&����u.>\���(��R���F��:[��@B���{�4��fZt�IJe�Z�R�
��J�~��H�V��(��m�k�����F�M���[����������U���j^I?a���cl5�w��VXx.���%�;3�N���FF��B�����S����[n��~�l�����e�FY^�F�����i�Y�Q_��L���.�Z������+	W������52��c�d�_S����!^Z�gp����lX��t��x�qP��Vn��"="��O�;���FK�X X��]<��5T^n��'��'5���X T�7f����S���5c���Pp^1���||���S��M�:�f��pC��
���y�����c��"�1�?�����}	`a!h��O~S��	`��+JV�@�v����s:(U�z���Ud�Xn5�z�U�	�hU�3�������{k
��O>#Zk� ������/V�����LJu������m� q�^�X�������U�����Z��E��x�����LL|�����q1�
vTM��]&
�b4���y�Z$g�6�C�b-�~
��+��E���������4^Xg!�$�������*�Xs��S��b�~��M�c�W����g'(7�"Y�$������7�$���g��4���)[�<6��X�������N�;'�\7����#6����}w���1[��T�0��~��5�,��3�8����
�G�@@�]�L����9I�G�z9=JI������j�O����<��MJ����W@���P�IBQXOK[dk	�z�w��e�e( ��'�`Z�.,�����$e�3}���*_�u���l������,�%���r���x�_w�^4��w~��\V�J���
��]����b=.����O������O��t6��2)�s�����#[]�^F�.X��h�^�0���h�H������)"|FB����a��P�������,��F��Lky��>tF<W�#����Ma��`��b�aG�cJ-�����v��;<����h��K��=�*C/�b���7�7!�����S�����`M5�?��������P#*�7.���O�m����vS#2�+~�����C��`U�cR�����U2�4�J�-�qv�n�y����|+T��U��n�Wd�����W�~����+����9F����[��=���	�cX"�D>�/:����K,n�_GP�����y\���<�m^��[����p�u��a�j��zJ��������]5���J�i�fald
w��������H�4�c��=r�X���J����>��i�E��?c4&_�j��u���#��B[DT����~��gqr<����K�c�}�����!���]q�m�� }F���h�C����n�m���i��bn�Q�3y)�\��k��V4|E2�QE�d=��"Wh�q���.��X��bF�W'FX�
0�y�x`�M����_�?�ZD�������(���$R��!���;S3�	�.�6����@�o
�u�B�����#e�
7�+���IE@p�|,Z�;�I�����#(s������e��&��_[�T�����?d��SQ/�X$��~��\�����k���K�`8,sG+��H��$����8����-��e�#��$�7c���Xh�o�r��)5B�����D5�-%,�J�������;���N��8y)[b�rM�/�:�:j���������)�4'�qh�����v���[;�8|�,<�-V���R&
��2j���Y��us���3��6s���9_�?h��_�f2���x�u �=�Ln��S"�������H{�L����A�V��c������S���]'��z3��jM��8J��	�������7���W��.9ZK������K#:��@���/�����<�W�N`�97?��p�����Ce�����"���2��,������:�=��Y�{�Z)NdEH�/~Y����4W��������a$���eO����&C��yo3���m����FO���v���4M��Ak�duU�
Xl�V��h�X�Iy�xg��O����"D[X�[|��S.���#~�N��Y�LvA�T�Xpi|�b�^uY1b�uK���(D�-��(C, U]�����V�^���B�����"B�X|P��������	�
�Y��I��
�n�����"�%�sk�E��6�Q��Q�\���QI|~HS��	��u������}jy���o���Q�-�1T�x���F���2�lq������&���FWNo�d�1� 2����6�W�4q&vb��?�~�D[�������+����%.~D�K�f�/d���� ����|p6�y���4�j�E{g�}~�e��A���g,]��;9��D�)�w?8{���E�K���Z�-���n �rk��O0XLmQ8�Y����b�b^�-����pJ$>M������+����pP2�#�s��n|���b�S���d�W.�o����`�g�q�o�����l�WlGaF��9��Oo^|�q�������'n$��;�B�s6�c���2��y��L��c�������A�9�H`�w�����[-��2����`��Z]]]U]6`%Oc8��>��m�(����-���Q]y3l��V�`�K���qZ�;�;�Ko�(:-7���� ^��A��	C5���9��9�[�;G����/08nH����i6�C�G��������	�	���4l�&GOr���`@2��&+olE�E�TS���;���]���'2\w���T���7�������?�a�������Y�}�::�4O`�����m����j���a+�
�l����f	�v,7�J9���q��|]e>Q�O�����i������J*X��!��������sj�����(���9'#X�����<������q��u�S����Z5�B)�JC&RW(�&�C\�2�&e�VS�E�6�'��
m{�r*��[�6��`v�����k1�z�i�]Vob����{�����J2�q�<�*�<n*�0�~1�0$��������l�������"�t;����r�{�yo9G!?]�~���F��u�h�4�n83:��s*lm��0�'?���o����W�B���,/�f#��xt��`�-���s
c�G�|��}���E)a9u���$��������W�������

��VpL�|=@���y�}:�k���$Y�1��"�X�`��'�	�Y������y��#h)��8��I�"�C7��F�b+��^�Z���<��t�D�c3�+v��>�U�!^����b�������*��x������0*E���6l��b����&�r��x�Lsi'�9.H��+�]��=��j��Ax�&���� �WZ���}�rB��* ^�F���.to
q<�����O-�����9[�K'�������
�<�&�L�X����/�s�l�� s�&�bm9�P_���z8��&z.�9��	��4��-����jW�������Z0u���mmTI��G_�����.|H��I��@��������&��N����H"�.��~��	���E����4�aV�w�*�by�^Q�� \|!���"��z��2�|����qX n��R�\uK��$a��A?����#���J������4��>h����l�a>�uq�,�?})(
C56��@��h��g���"�f���:��1M����S)����*�y�B'�/�����������x������X����SJ��+���06	���I�����U����O����w�H)��gWH�:P��b�kr�#��%O,�7�
h�q����c���9��=��
�+<�
��39�C\��
���I�(��>4h�?�����5��*j#>�b��C�\r����X|��Ad����!1"�U���>
��/�s�n����[����O��)����`1��W@�'��w�s U����j���Dl���Y���{~{<
so@,^���G	C/��\�o����99�����+��-e�
ee�#��S����b�+o�v���G���L};�1]�����=��h��>���R�O�R��%��X6
���7��D�X1++�/�]��iL����u��xv����:H�O����S*H���g��nN�������o���+3���3�������%}S�]�Y��[��<l�=kw�{�~��3����y�O�~8FC~�~�3�^b�W.��-k�Tq�e������U�J�\t�������j�z���o���5��H>
��A�O��w�Z����*��]���s����wxdXAR���j�� �4�����p��;0�����7{/���`:�M�'�"��:��X��8���c/A+&�X�P�W�������f��v�0���!E�sAW�`��q����������C�V	��q���sT�/*��y�}V�*��@<t�5�q,����[��-u�o�
	�l���ze+q�N@(o���|e��,�y���m���ok\;���/��gVO��g�V�}��b���C�/~��
����i������[u7{�� ]�p�lQ%��M<�5O�lE�������D�u��.,P��������x�W�������9pH��MY-���k)ut���r��X�����-����M"s'�`p'���;u
���6��R��T,>��0���2Z�=�Y��=���l�U�79��E����"�<�]l����AR}�C�@m:�� ^�*�4�(��#���#E�������\Gc�Rz���L��\����P���?p���9 S^{�w:~�-����|f*x�K!������0�NTZCvpH-��,��E�To)�aH�,�sl�>���q-���-P�{�Q���xG��3�*1�F2��C�\`�Tb����1�<��;w�>Qoh8�P�\�!�P�s��TdC��)�b5�V����*��KD�g!<%'�P7��\J�������������M�"Xz�
e�ufx�����N��������y���!/��,�x1��E���A;/���l���-! ����x22N��.����%+[�b)|0^h���*i�>�7+G"����Z�l��$��W�kh����M�����z�~e���$7 �-�����2)�u,����/�����sz�j�vi
�z��pE����2\�M6L-PT/��Un������n��F�q��mr+
G�D*T������,�E���/����@_0B|����;�*#�#���Z��;��7�#N *�9�E�6�%���HY�A"����$�a��k%����bh�e�/G
-����0��5��g^G���4��n���i����r
P����3��s���i"i���[I�B�m\v�D|���p�=�������O���P�mZ��\�cR<�,��@�
}8����t���`���A�����ut���]�i��j��p��K�8�X��Ho~���9F��6 qq����EETG&!�E�N*�]t��6yr
&�_n�un�h��l���v�E��w��h�`#yO��b?���[�92����*c%iUW	d0�.,��
8�+�p:
$�*��
&w���[x7�_\G~�R+���+Nn�O�K$��1�7(�o�w���1�'���BM�p�x�2��`t��(�*�����`|uE�����!}S�a>�w�RQ���v�J��D����������`"O�����P(�cwT)zCv�����e�U�D���QS���kU�����s3.�S�e2��UeSz\yu��WL��������U��l����������{o���������W�y�rYG5Jvn%���fb���$o|Vp�"^�$���yd6Jn�Q�@�$�"���z��b}PD�|���sj�42���+f�/�?�&��(^[y8�8B�Wc�.dj1$� �ER���v������8d����C�}�.����������@��Q�"�;�s��H�q-d��B{]�Vd��f`<��DlD�g���������$��������vn<�C�-P�j	���l}�65�1���9�M�r@��:,�dX���Pg����pL� ��L��]��c<��W?$��>:���3�^$���n�=r�� ����!� �Ek�����T�Z�~�u[����r?G��2Y�m���!dR��_&1��%���(�����������`����
��XuUL5m"����^��.�`���X��A�b��p��A7���W��r:��
�
8�}������q�g�b1���FC�c�����I����}$kH��NO�wB
�L�/�\������GX��P�l�c>|A3<f���m`�'�)���G�������������~Uw�����*�MY�F�~�?%*�Bp�V�h~?����h~��0�D����n!���@�*4��u#���6����>������g&JX�b�-�KJ��]�g�P���M�Y���q��V�H>�����9�j~�Zz7���f�A0�m���;l����8�������	��m���&�(K�ec=�z�������8�P��*{���s�������6V�k�*n����^�r����9C�\�[�F��
���;_��@M���<(�n�������p.5S�!3}zL�$�JC��F��r�[�"`]M����Q�n��X,U��XH����������#�����&����5JV"'��D���b���c�N�zp3���U�PH)<�Y"D�
]����������pp�����mu��t0Qi���e\:�B����
�d��)������p�s��`����s�����`�8����J]Y%������Y
(
eVJ��>�5f���7_B��heM�qyK�7/Y$�n�C_������$K�����w��@�+������K'\���
�rH�����������o��pr��n��4ZR� -��������?�H����Fj/0�"��U�����,��x����y��y�Uu�Tf�%����*�`��=[�C���C\������q@��,���RI�?��d�z��
h��>�u?m�N��#�p:���.Z�>E�%v��0v��\���u<tn��O
v��w���<���8WKn�h^�V\����s�.f@\��k��=M�����qv�<>u����_�_+����!s�.6��i�����=���$��uL�C*���Q�8�A�y������7BC�Z���{�e��^E���569�8xc����L�����lf�"�S��-�H#���`�L���f�
,�ov!��)�x�OlQ��PL��g���������`�?j���(pq��S��v;��i�q���+��p�rK���O��p�3���m,�i������9|�
�p9��
���xPZb�X����^DYU���2\�YuL�i�"z�s_����|2�
�k�`x����w����N8�50����T�W��T�31"�M���~]Z��d<%�rf�$�D�/��Lqx��V����]E]�������(����s����*�I�/�p����*�}QV����r�E�S�����V#jR�i�lY�cl���:kvZ������HJ8*���06����)�&��/@'����(8�H!`�Tb��q��"��7�Z\���vQJ.�
��_	��l/g�{8=(��t�F�E�K"#�t��;wl������>�@ P7&���\
w����$FH�j��Q�/�GEDn�[��D���
���
��&[DP���^�����w��U��	{9�jj�w��i�'��H��tt#����bLn�S�{0s�
�N�>�y�QF��[
�i�|� !�DW~���l9=���9��4��������{����Y��kY$2��?��$�%31#�m�/�LEy����g�v��*m�K�H�4f@''����2�E��
+gR��5��:~��~E�+Nz�<��{k�+s���P��0~'YUb
�#b�`+�@�3:F<T�����(d��	���.�G��L�G�@��P�|gmPk��B���`#Q�q@|��C��,���T������O����z$/��?�B�`�	����������?0����7|*.��\�����m�%<�����k�`��~�WE��O�r"I�]9�D�+T
n�R7�D�����&a��m��b"�����x�q[����\/=%�����o�'�����U�K�#�}W�;t#����P�\t���P��\(W<�����Q(��U u�6^���lg�d�����k3�`�y�/��:^�H��BR��X^pA��%:�pV��"��f3����vQ����/�z!4�9�m.���*D������G��������)�����������=]4r+#M`��K�&�+�<�l���y�9�������kW�^0Z�g�x��,|7��nv�G���v���FY��E�aU"7n� �D	��EF�,���$�lW�r2TR�},��������S���z�L��QW��O����z;f�.��.��OG��
�!�����3�`��8G$Rh�Kn�M�����{z����P+?@.�8��*v}7*1.�1��/���n���`@�����
�#�3aI�\��;��DU&DQ�^������k�07�j���r+�����; �w����w(���CV��e�R�;p�;�+.//��1;�'Z�\K\T4zf^�l@>�6��bC�����pn�E���(E����W�"x@�|�k�.��j�V���#����)m?�@�Fc�	��C�&!�����Xi/h@4�G���|K:'����j��I=�B��E���-�JY��!1�����X�L�J�@� XA�^V�1�d{����]��Z�~��� *V�$nUkb��~0:���H�����{��:��B����ySd�l�������s%H�b.��<�`O!���n���l4*���C�E `�B����8e+��rWw�/g�9E6U�����z���nQ�!hg����Q��n8�������!PP}��E��lt?�/��b�����0�#���9��L��Ve�������p��CQ$@��������4�".�Q�L����f<�hp=;���i(?-�Co�����K��O�.&-L���-Y�,<�tL�K�{�G_X�O����`��6X[u�B���0��A��,���^9�����q�d�����g������/5)�"����FG�a�1�n�>�����s+m��E�*��V���~�V?\������o}���`����~h,�������m�H�OW^��V����e���)#�T��Y����;�/���f����u����/�{��W�a��4���x�"��+��:�/�BE���t�UN�9��:Im{�l^Ei��0����{�3�.n�S��8G������s�9�������� ���N*��A��S����u���iu�h�m�����a��r]�t!���k��O$��jD���
�U��o�(�M8�������?�ku���-��d���iq{���<�:C)����=eT���x���A5G�,���aN�@��<�?��(��)�S�qk+9(�����h������b<�������NP�*d^e����1}��e��N��@�KG>��f�x�y��un�:�-������1�/(}In��,���V#ibd��+q�q�h!��A-�`$9t%�3����������l�wL0#��}lD�cM���y}�`|�Vl@�f��FV�W�(����
t���D$-�0�7���$���K,�LKt(g�����O�{������'��I�O��T`�H����������]�V*�b&�Qw�f]�X�V�R� �$#�}��H����G��A1�c�X���g4h>��C��`��+x��"��������8l�b�S����P���"�ui/y�H��n�h��s�!�����"e{)����hC`�(��_�^�����r��v���(m+�oN>�g0[�L<l��#�����r�<\P���7�:1+[D�e�-�klS�!\6�=�=�*�vT�K�-�7�P��8�����1���Fg}�����WP
��&��pK
H^���b	x{K.���G�����6�l&M�{�=�?X��W�8��O�P�P�8�V�=�w��s���LZLD^e�"m&����
������Qu���� �y��q�������m���@��v�+M9#�mKKl���;��LH�������/P�q&���bc�X1�O�2�Xt��j��h���������}�/ro�Es����'��zN�����wV�	�i�zJO|�#�b���I��K����yCq(���������d��,KMY��Q�t�y���!���	����ubB-��:�����pA�I�|;t��|.�|)}�����;U�$"r�]G��6F��V��bV�X�D<5_�Rh�}Z�{0���~�����bF��x�(��*��X��?�O_V(�������zH��@��	L�T�I��P�H�7b�
������p�������7��#�X�:��C����M���u��z�^������4���QmQo����L���w�9YShC�KI�-�'|%W�;�zr�r�,("���Ub�U����B��5�c1h?`w^����@teR�v������Y��@W����g�]��"!�>]r�hT:��d�!9���
w�B�Q�/�E`>�pqx�O�7g&@ao��?�(o9_�T�� mf������
-�������l<�F�m�N{���	I���,����P~w�g�H���+	u0]o��X�v[2�xk��%�����~.�����T�
b,X�g�PP��/��d�)�i��8���f)a"�l�B���/�x��Q�
_�������DxqRD<�����"�B���TWX�}|���xsn��������3������dX##��Y����{����l��������r�8���G+{)$u�[N�S!�).��g�!_�;�u����9��c���4�m0z�9��UM����z���N|dS�3)J��U�^.0��=n���+�M�%.^�jU��� ��=�
@A����f�W#�A�����U<��[dgF u�,�	UA3%�i��z��k�X��H9�f����[�0��Fn������O9��%^4)H���#C�c���c��aGD]k9#'����C1=�gE)�6�E+cB���Lt�T�z
dQ�Ru�e@< ���(zwDU�r8s����������Zqj�`����$\f*%#���B��q9&��w'�?t���.4�%]�_a�
z�i��W4>�t%�w���7��O2�;u��M��S���f�u���{�N�"{���a_j�DF�=�=����v�@c���x�U�v$O�L��\��FFL���p��9�(-,��	���W��L�w��o�
Lg	����������*TFS�W��	]*"�'�vcw/t4F�w?� �L��F�����Pr{�P����Z�x����Xl���d
���d��������L^c��S��L���q����'�)�������s-Y�7�3x��2-e���&���P�go��.H�-N���G���D����0S�����<A�e@�zb���H�����}��������N��������p�8Zi�O�ogy*0�:O��6e��K�<W 7m�Oe��,��(��#��v_%b���:�L�B����j�pUH�l�f|�5C��!CeWq��4<�IV�����{�&���%+f���<��|&��!��o0�����3ag�����F��^�y��_S��W�=L�k��dy����sA��`X�c������$8R��ad`��W �������A�,�\,��El��5PQ��!�x��h���������RP�3Y<�9�����J�������h5����|9_�y��Y�E
����0P����h
0D�!����q|��o	4�j�WjQL2�Z(A��5��gb�}�y&~
�����k�����q^����\��V�
T/b��B9��9�'^����yR�3�~S�32)�%�D��4C��rE
i=�[�B�I�!��}�P�
oA����,R1~`���g�u(��dd��!�T��G������������a�^pr�g��#��AP;o:��77�1<�%�2�!��3���%Dt�%>���q����p����#�����<P����h���z�}��I	�.��+�����H���v�$��B����dl��^�q(�kl&��t���K~��d{9]^�q����%��m0%��x��#I��,�{�R�wS�����K(�F����g-s��YZ"W%���)���jNC�����\�s?XX_��l��25�k�\Cd�J��yf���>:���y��XI?���@N��s~����������T����B(r���(�)�i��'
q��Fc�9�;�m�w�
��^�����"����8���Y��Nk\`������p���H�����|-&��\!CJ�����C*�#�9C�o4 �������F�T%�H��}p��q(z�+�vb@E=�a��!����O��8�K<���$�K��Q�#�*��%o�e0V���B6C���`lz<\^��r� 7u�DtE��l�B|V��������&��[l�
�aPD��i|P�����k�y�hx�����'����g[*���|GQo��R�����EMX���Y�>��g��Y���+o���	���.GK��k���JF#�R�SCA����%��8T���$��}X�>r��%f��S#H2E����9F%tB���7K�p#}��>�f�����z���L�CNysG����2�C��:c/<����	lH%?�8��Ki�o���I�L����~(1�����(!P�4Q`ZY��0���v@tNY��1{7�����6.�8���a�8�%Fl���e�0i����"K��Ez������l���`���x�,�(��^B�jQ�s|�V����Xf�*&&�o����m���E���)�v0]M<B�Dk��
��Q����mH[��}x�2�����$�S[�����.�ss��}$7I\��4��������g;a$?��E�����<�.	D��(��hRD]�n"�hq2�F�1G�rx6�z�o�4�����fw+Q��Tg���:�h^���9]�o����u����������|���o�����t����"�U�B������F<UZ��S��y��_;'��mt���N��eB�(g��l��!^�3>q���`=�m�h�����d��D��@51�����!f���Q�vL`Z.|d��F�c�op@b~�C
Q
�����(��Q��=����1V+>��`dv��|9d�_t����X��?Z���y����
R�f#�O����{
I�,����'*�	����.�������d/����#8��8��������g}l��"Qk������:O��1'�v[�XZ��*�4���Mtnd�
�H�q����/����/ M��>tcz�@2Zh0	�����.�a�x�x?$����.�^`�F�H!�r�5�m���m������u�EcL!:�R6,�����':=`~��E�Cn�V\�Jv$ 7�xVV�����48���������s�9M-����z!x�������?���������r#}��4�+Rg��b��l�y����������\�D�*&7�*p_R|x�fs�����G�����*�Jn��y�k!C_[��-��Ib���c(DR�������+�G\������ag������)���'V��)�`#m���*��m��MT�f�^�V�j-��sq�y8���kXB
�u���|�#��#�R��9����*�e��?��\���q�"O��c#3b2J�r��Y���"�H���g4u[���z������FW���3(_p�B���
O�<(g�����P1�W������.P�����?J�i��2�K.cZ�b,��S�i���>��u3�z���y�//�v�������lD'�P�w�.f�g��ix�1���������s�
o��/�3����M#4G��s������A���*9^3i�DF�\2^��G��S����"*l�J����3S��2�O�Z��O[G�6�2�O��T|^����J���f�\-Bi�i\i#��c��l9x��������dw�LpY��\w���BR��#@�H�&���}��(�.�H
��p���V��%�3%X�_I���f���;3�>����n�mHe�-AH�.'_�����'K��*��J�C���&��qJ����"�(�d����Pi.`#�@��9~B����2�r���Z�_�����*V�n��=s?����}|eR0��}��?����!�������K����so�G�*��@�:�/x�!�I�n���`M��#������(�=�'#~��?S���Wr���3��L�y�hv��k��S����n���}+���>�R�����-4G^��0�B���
���X�30B�o3���i�(��v5�P�v���B>����L��{���y��P��S���f��$�5��s8?d���(��r��U�)��N�R�Q��.o���E�ap4�n=��!�2�����E�
���[����`E_2;�nD�s����!3��Q��YC�����*�� �K���H��(�N��ek0��y�����
[��j�j9J��������a���;��7��!�N���h\���������g���P��(����]�������ZR���De������#�pi�Ln�
�S�V��xH�����������~$(L����|�X�
D�5j���`KA��+�[�������u!._�P���B�X\��"�i�������y�|��(��<nI��������Y�!>�:Z7l��q�����;��NG��?��}���Ot�����h�\�T����c���w�'V_R�	��p�>9{
�M�����J����7j'+wZ��~�t���+�����"�|�c�+��t���-%�5='/��@�W�
������w�<M"]���n��i�i�c:j��d��'&j�<.��KWwS@gc������$����J��r�H'���O�����BQ����'�Y �U���k�@��!H�~Q.��zs4"�/����a������c�T��X��j��=u;~}L@����������C���1�O���W��I�-���Kv��9b�k=���S�� ~�����Bm
q�DY��>v��>j���u=�=I�$�(NG�0^�����K���t�DrS��b�1v�"@
T�Rq-UA)��b���C������M|�@�@c�������i�`�q���9>�0�F�^~> %k���Bb�N&�M����E�Q�GI��3�ry�B!!�_��z�v� ��������-".��a(�UN��?�m��-�rH`�8���g[����i�Q�.(�a�1�W$-V-��.w4X,��� ���;�{h.Q2S�}�I``"�����8����u�%�|�\T��F�8MD'RJc�;�WU:�J	Z�[����um�N������%[��������FB;n�P�_wgRj�Kn��;�
a�W�n
7�A�z�;�ix��g�O����j�$//���&9�<P��S��\�i�F�U�����l�v�����zq��:�� �����6��
�|������>����j9�����n2��&~�m2�8�Y?W����zz�l�1�����>�������H3���t�vb���[.�����%}=8J��ys�#:���}��)yQ���Z������x����B�
�������_l�C�v��'^3X�6��e�� �g��py��w
���+�]u&�(yc�"N�y��i�Ix�P����0��OV���*�X����^
V���@.��$/iR
�	�W�0������3ph���5%���?
%2zmS��.k^����}]��e�}��?�������i��=Y��'��������_�jZN�����]��kn=���":GR@���LbV(�����N�����YLB�@HU/�0^/�I�b�B��YRf����`c=*�;�k�!}����8`��r��+�!��[���vGV�g�"���	�����DS���"�LB
r7�!�����OR��[B�_�A*9����]�Hn�]u9��_�$���$�P����ED�gr�U ����!An]�^�����<9o��D ������>�c��^��a�����GY!��[6
[��m�l����/V�����lo�%���Zb�.��.�6>�.������p��t�B56ev�������%�+���A���"5$��\�!�viT�z)v��!��U7C�O+�UJ�f�R�D��mH��f���<!��HU���9��R�B��b��v����[9�����f�[���������*O�U��P��AZ.����')��
��������/��O+��7acp�S0��z�n(�?��2Ux`�6<i#�6��"Z�(�)=���]����+q]"/��z������C�\�|��]��{	�0���������0�\�G�V��f!�BT�o�(������}
O��/�e����#�%���4�GG'�����V���lV�psq%B��B|?4��W��*�L��?/��g����kz��x#S |=~/iP#������:��,���N%==��
�;8��15�������}����z�Q�d�Q<�}�t��6�-�����Uqo.Z���g��Y��*�%�GN<C���@�^����k��u5�pt������b���������b�8�����IJ��)d�=	���X��,���D��8yPz��r��X^�����jU9qzS�h�i�y%CSs���'}�54� �y��
Qv�\l�E����sv5^ps����qds�8xA�Q(�'6�"s�9�l^�S	�(��%#��\@M����q����g���|]����z]eI]��rX�;-�sF����g,77����$K�A
�y$u����.�W�Ic_� y=
F-����oQ��� �+����uRA���L5
�.O���"��
<u�z�'TdE��9�C�6��=����R����Oe����s4�8�c�F�j���o�{�o��l|(��X�"6����
z7)��i��h$�����3��Im��[=l+$����9��2f��Hv�)c3�� �����;����������|p��]����6#����4����xa$��x������j��+��������8�C}�(T#rA�l���P��/�
�Tq-V�~����������/�O*E��]Y���BrVTV��1v�-|�(��pGMq��/��;��{R"���9���!~#$�/0�|��y�%�Q0�c��N�B���>��*�
7�J ��� g�v�TtyEW���H����f*��$XIGC�t>:��@����b�#+�a����x�Q_b]A�%%���7!�&	8)�U�2`���a6oC�!^�R��+DS������K����x�9lY������)�!��C+Uz-C;E���(x�Hp�m0r�
��2Z1kD3l��n�x���4Mj��N���uA�����	���k�=�����9��:m��Os�9��"�<�?����6���XY����{?�� Y����S�a��g������<x�R��D�g6y�19fh��Q���� �gT�FWA.��!l�Xa3X�G�t0��M1%������pZi����r�n$���eU�������
���u�[�K��C1�{Sp����Gf�\�g�����dz~}��U��=�$=>+0�f0\.���S��ag�\pK�R7��=�'y����E��:���	*hL��u�n��-b�JNUx�#&�e�&��+z�����S�`~��q,���`���#L����������x��^�������-��MJ���~�k��L���wu�YA\�u���f���.wxM�Jr����F��2����&��gX
hq����w5����4�:*W��lL�up�Q��Z7�S�7�X���6dOD�K9R�1�G�������T�t"s�B����w��A!���[o�w���K�.{~0��g�%f x��x����S�]Gc, �����QJ��o!�����V��=�Z���
�L��)0�S`O�C����NbK�4|�,c��������:��=-�l�����0���c�%��9_���|�\rke1 ��8kf>.�_��=�@�8����u!��R���7�nBT�M4��L������tR�o�*'��	�R5���������=ec(�j	���}2������`0�r6R*�r{���+�J���3�>|	p�*	 nP#��4?7;���oV������9.a�S���3A�*�����jYg��Ey�f����lF�`W�8��~"N16���A�S�(&&����"��-Qn��0�1��*AtJlu\%�!d@'��C��
?�^��
�R3R�����-0�$K��#��>�����Y���LZ9��R�0�iW���N��H9���v�rd�����#����}]����rD�s���}�#���>+G��4�;S9�o*G��+��"��L������V�hr��8&!�VzOC������40���f�1J=��6�_�W�JAL�T+�m?"3�����J��K���D��t���-�v_mq(!���A��/��u<Lt��@�:I<��|�<m����A�F��8R����U�>m��[��Z��e#�g�����Jy�R�<����[nT��D�����i�0���9}���0�;�y��Sg�I�A���#{_*X2	b&��<$�K�y�Gz������/4$�`bx���!&l��������M|�c��KgGW��r� �AY����n�O���-��������s�t���g0����g��p+�G,�9#��%��i>�����Q�k�6���:�`����	��eW���
�Wd^������j�U�R������J���^v���J�E����a�xLK����cr����e����H��
�3���of�!���/f?��4���=�Q����N�O]�%O0&l}~��c(�"������M�#���������0@�|���\$Mo8��~�F	������w�v]��Ibt��B�e�R���5vqYK
9/@](�B�-@$�
�@s@��~��&�����tEO�f^\���U:@���^M��0|�]�t��x����8gD�z����W&����+�-�cUD�e��s��Q��H�����Y//c�*U���"V}7v�u���<vU�r�bo�����<��,r�3v���I�
kE�Gr����C�������KM+���b>!��	��O�g�V����1y�v{�^dfX�e��C@<�.<o����s����{Euy2#?#��~���p�7�R�9������lK]���Z�����k9Qa�IaH����%	 }��X��lkMf���]�q����|���������`Rz�o�x�;a�Q�F7��?d�k��	3����7~�������~c�u�VmqS��/4+l�AN���2��7F���YNQp*�5��1$��C=�Wf��J��_I>0Ea'N�{�Aehb��t7f����w�.�&�������^r����d�l��#���y�}���F��6M(��d&��`s����L
���4a�������	�P��b�2��8����������i�9j�����2�����"S�����q/��*�=R����i��u�}�|����>���]$�y�{�N%�p��������r�g$<����G���1�r!nD*S���������%��nh4P�"�L~B������&�\�p:�{�*]y���b����/_�m.�~z��j8:,pM��r��::�3BM;�������g�5��<���~�c�����]ba����T���
u�P(���$����mA�{�K�,���rdB����e���H���P��H�1~�9�uL���4�� ���hpB�rAPY��S_c������p>^-�q������������G���x��-���3�������+�c�G�8'As��6CV����lh?�&�p��4���lR��8���n�����V�:j�
��b���@�J����[�Eo5A
P���y��&.yop7]s��9�m�)��Q��[C�Z��v�z�@o����=h��R����nX��m����N���<�
��l��~�YT�$�h�J��j0/����2/]�N�JE��d��������2T^i���e�O�U�|�q���a�����>n����#�V&L���9���:>��{eoSN�5�=�^$5�	w8�n�N)+�s�Q���Q1&FM��H@5@�����
���5�H��rb�y��b��)Re��������[����� �^�U�}�x�eEK/b�������Bt"xBC
h(J�j�TrF�+�U ��1��
����i�F2�R���'K%����d@���w	����$d�$(=1��������.sr�e���%�Ey&����+��g.�W�[n�/�#�(�
(o���C��Oa�������lR���AH�T'�5�����keI�hH+F�@ke�9��y��� V�J���U4�}*�� ������El��v1�[��Z}6H�������	a)�qH�}w�	Q���{2�=H�����U�rF%�
WM!������o���%V�L���(�,�"w0���Z&�E��W��?��-�J��X0��bw��(/����XD��z��z>c�9����%l6��V>��qc�����Xq'������El�8��]��k��[�5�r�x������
��|�T��69l2Htt���&koI�:n,��2����_.��6F���%�����P�8i���$Y�l���|��.��i����������3�u���9����=q��w��������a��qzInV|-U
.|T#��	�*	��*b{ID6��+x����45�Z5�����%��g����x�����Xda!�:Z��f�K�
&��ci#Sp.uZ�����J�E2*��c�6�t���i+����)4@�-��o4�8�ES�b,[[�@�w�d�{O�~4�P+�"������/PwF3/��PfQ+*��U�TR���p��0��M����C_��#��z���".��k������V�xOAU��=Qh���v�h>����b��^�g����AP9��b9��E�B!�����p$����|<|ww Z����*4[.����`��EmhV�7�rA�5!�D����t���#f����G��P#�����~Q���<��O��E-�9+��b���"��^EA��+�C���^~�-��}�^+P�\�����V�b0L���YW U�J=�x*��@ ��������E�a�J�h���z �'�b[US��\��o�]����|��i��Jn��*!�\&�
�R�������z�#'b;�Y��4,@���c�M��8���3.��[*�q�����$����Ew��|�
�1D���N`�jO�	1�����U���z�/��0�iqw����ld��{D����5�����'!9����p����
e��������!�r������q��(�WQ�A�PZ'�O���t	g����kI��f�"�	����v�����4C��l����y|�F�j�(%,���3�oU(>��*����
~�I��P�)��R�p�`��E/Vq�����K��[�L����R�)�"�eL�({
6��z�g<31� ��%��IA����a�����I�s�2�x��U�t����S�������T����"��4>	����)�b	��
���Lz�+D&"����8�H���7j���s��Dbg��-g�7��ft�.�|�����"=���NQ��!2�8B`>��|qm����t5BO��F�]�lj?������.�x������JQ�g�/��K�Y0��Q�QQMC���M�/F���%#I�R��R���|�=�>�q�q�b���2�;�Z;���FShZAa��|�
��"�L���I����D����+��M8���(���Rd��N�{*B����dZ���2���}�-@���9���%������.�c����`<y3�8��Q��n���y�%>�&s�z�<?�m����aj�J3�����97
�v�2�?u��;y����(�	$�����X3V*]��:���vx����\&7�F�%>+�6��NT��1�[��D�I�XW*n�L|�a:��Nt��q��l�N��h��1����v�A��f�	�����Cv9�Z2y����hL�A@FD=�p1�i����od[�w��|�%�����W����"��
i�0��R�e�Sy�����;��R����DO
P��,�����9UGl�d���V{��n��B
 ��v?e2�����9�����.{����	���Y?�p�#������X�L��u����H&��,]s�C����b�������h�P�7�/|��X��>�(U�n�f�����7�}�88M��d������~��c�]�n9�J��S�O��*���8���Z��N�=���(��gW}��3�Z\[�U���J�C�T�)'�������p�i�_��n>_��O�����|�=��V����G��yBM��������u{��g���fZ�?��c��m��a�pJ���?8������^��_�	�9{�>�}�O�?�D?m��r����C���w�/*V���?zh�}�>����������1:���������	S���*�9~��+�U��~��Ae�`�)��8\+��I`;o�Z�N��M�A�7~��G�{���~�:���w��?a���_����]Y�����	_�,���?g ��_O��������0n���;�����aP���S��s��{x����''�5�f�?������I���^p+���{�I����:yJ��������r^rx�J��UM��?D��an��S�{�[8���&��s����q��x��b�!�����xrg��X���dy����&����|56Y��bvq<C�Z��	���S�r�\����������b|�=�B�Qu��h��� ?#u�N�����:(�sq\�:*�����2��������&����^���p����'\^�hD��>�*�������"�lx���0��p�YFP�	����L��B�5��d<7���G��$���5X��z�s�K��Z8P>�IB�9n�Z�T�'�{�n�5��n�h����l�[r�N��8�����6tQ��K��N�5@;�����'��k���`�)��X��3�R3�2r>�|�?�����V��8q�`:<�{A��5��+��P���R{T��l� �g���=kf�P���BU�����L�H*+��P c�����8R�":n�j�{!�xjmp�y]vo����b0B�1 ��I��Nu��.S���5�]�p"(�J7�='��b
����{�[e�#p0���md������:Mz:��f�8vm�&3r���W(4#b�����(4���uV;�����gQ���!�5��&���W��7�r���Y�vK\������M��|!"#T�
[0��")>�8��4���w��k�j�V���$�[�����zWV�`��������k���5~�wrY�~+q�����C��6KeK&���������u�fk����+�B��21m$8�3�(�_D��}z�~���o��b��D�{���e)��y�u~z�~��EWa[�v��xc��+�O%^�x�z{������t,4UO�������49�����%dY�-�\��^c�����r�.�Y�����?���z�4�{�p���Jv���Z>��G��G(�&�9�>���z���M�����^�n$��Z����l�.�D�������[`0��.}��m���`��5�����^���:g[������+���������A�h���
q*�b��	�����&[G����C�\1�J�2:����w=���?�}�b�=��^n�\��?��1����S[�^Z�5*��e���U5��0�VG!A��G2z�wF���Zk��;5[?����:�p:u[M*���2��+�F�W��$��"�#����8=�_��������f�C���b�?:>�;
�s�����]f������7�I��������Wi����<;�1AC���Kn�9�-���{���N�
����+~�����5��Y���)M��V����YLWh����vz�C��w��88�b����O�2o�F6LC{����%V5���U���"�Bk�>EK%�{��%,P2�)q���61���D�K�6`�����*�>��#���6��v���j���|t[O6YG�7����=k�S�rb��L"X����(�Cs2��&����u�R��cks��?��`|�/���vg�i1p�F��V�U���N"�@��hK�%	�6WW������&��o����[���O����Z�xb,)�C�,%m�	)�����*�py!|$^��o0�E��|��|�V(�?9���V��*t
+�[
���Ll{�V�VR�5�����u�V�>��1�g�m�!���T���h�����8���:�+����z��gU�}Ls���M�} dl��^��]�h����P���W�P�Z)��1o�-bV'G�p2��e�A�V�5�
i�'k�6�g�������2U��s�H�������T�
L�R���%3X����k�T��E�Q2A�V����:��$)���z����w��(�*.pCrmj�2:5����(���.���Uw||����{�5���V��yB�:��XeW����;��
|�����H, ��j
�j�
��&���%���g��{�`SJ�[,*���>c�4�j-�n��B��������?�L/z�Z�wV�Go���TF{����qZ���O�6���*'�dA+o_��H�
�+0�����'�#$�
PIu�<�_�N����M&��
�{(�&���)
�l���Q%�oo�
��y��H>m�=������t}R3�5���F�-��U�^*��A^j���(K5����FvfB �,���if*MW�=F��I�����Y���ySqy_����E^m����v��:��yuh^N��[���8�*D�7Ra��bGAt
]�v�X�U�r�<����IN'R�"p&�C=Ij���������b�\�+r>g���ZS��#�U�p(��b�`����;��{;�qA|#rE���ms����Y�b��@�R9�Q���Y��	y=��(��T�h�ld�{?i"�}G�6(���p����A�����p������
})��h�K����������4=��P��������c6Z4�@B�yik0���q�f|�_P~$�RG�7�o���\Si6uL��i��d��c�	�`B
 �,��#L�T����W|��k���u;y�����/<g��y�����K��)&����!0����L��1g�9t8����(�����gd���k�ys����U^�I��N�����+�q���ms�)������������7���mX���7#���M-
em�{nji������c{.�����������Uw�}�T2E�So)��Y����C���B�Q_8y���8,���Qzh�C%��e�N��K�����8����0���TL��27�T���Y!?�ySY� ��D
������0]���Y���N]�U�=�&4��`������5	D�[�l~w���j����q�����?� �>�#-w�U.Q�;����1��*�{����d?y���������T.|����1��%���Ra��F�-��;�	5�d��s�rG}bO�W"m�K��6Z�MV�RF\aV��me1e���kY	���r�
��d��n�J�-Vc9w)NN��*�M��mS"6`���u���],�����C���/JC�fGt.9��|2�3�LF�KFQB��V���y��l��L]-��cR7M�S���R����2�b���9�$:�$m8�{���������j��sA���R�0`�1��g��5��S8`8o1�V�@e�d���Y��Q���i���->en�P���;��@������:dy#���s���_��y7Y^QzN�r�Z���%J�
��
N��r:�
�]�H��gy����<����=�/a��X(��b���/g�HO��O�6����{��&�^3�Z�B3z�D�(uzz��$S4��0C������H�\S��!;��A�VU�	j|��j#�����,��^��������(�x<]������($�G����zx�w Q�*���&�,"�}��������}U���d3�s�!&��.J,J�q������ex����[�$��(��j��CKh���\"RC���x��w��%?��~> �hL]JB��}!�`�����:m�Z/99���o/���,%��W�b�.D�2��������T����OT��T:Y�+����%�&-�M�=,���J�{�)o)�l����)3����#���h�4��H��z��#�_��j�|�N���I���W���X�v&����#8Q�[I�I��YoCIQ���H����\�Ar����>F�Z�Z(������	^c��	7�*�������~(y=�=zh(�`�7���8d��b\�F�~�$>�^L�W ��h����GGfQ+;S��emPi�X�XJ�����6	rpWP����`<Y*OWnw�B�M���#�U,J��O����:��Z����#A����P0 FK�.�0Y��P\d1�/T0S��`<��ZU����O��S�y����]R���d<������Y���w�5j���}�����Z�G���a��C/<�4��-1�he�1�3�����)��F|48��c���_*����:�������?1�B��|k�$�����j7J��_��p�y�6��~������+��R+f��qb�=[�>���z)qz���:T��3!H��/����O|e�F0�T)�8��j1w�c�g�<y�r{U]#�(K���u<Q���3�ab����s9E-������('N���>�VL��8[F��X�x3���W�3%d�5	4r&G+�I�;3��Z�j2�B�*�[�)�
m//W�u�����ge��������.'5;�,0�B,���1�b���Uea?��#>:k��m�����3	'�M��V�Vs�E����R��i������4��J������+y_"���Y��V2�
q0Y]�#�f�b����M"[�G�N�oPqn�+X�tv�������?��
����<�����1��c��?����<�om��Jn�E,��pD�q��s��$�X�]+T�Z1nPO}���/mo�B����OK/�xg��M
������r9���f���k;Q#�Y�b�e>T��Z$�#������!Z�)��I�7L��b�8<��X_�2n�p��x�����gk��
�F#�x'9K�@������uC���5C��F";<�/��/�(j]�U�[��,�(��}����#'�����N�0a������^P]�xHW�z�]����B%�p���;2c!�D��W�,7�����U�xv���@��;4p��>����Y��*�L����3j���]T�xX����e��e��+d��#'I�'w.B2w��<�$��\QP��������u�/B%�%#j
Q=�afJ�1�%��s��Q@��-)�X����~(A�����7�"�PV�	���B����\�R�����_f�H�E�e���u�����mQ��J���j���
�y�j>�$�����`rF����D<���R�r`MY��o�m�g����O�dl/U;�Z��T���LR����7���J����)���o�:�n"z<��E��o" 	�n�'��,�7�Y��B���B*������)V%��?���L�r!-�2v��+�e
/� �)�j���)�
�B��R��TU�
��$ET��2 ����*8� ���b�����g��!~�Z��>�
o{z�'�����
v��A=2Co�W��Z���E]��[�.:��(�`�$�Z�V4�^L��I�M��x���I�V�����h���(oB���`�b0Z�]`�J���R�P[A-):}��Gs�=�;{�W�L�������X/��jOB�K�p���H���|��� ���qY83k�u���[S�R����Y��S�]s|���-��2������'��������!|�g�aH"k���;)���+�(��W��Kn~n�=2o�,����A��9�x�����YhR�x�)�7�q,�����Sg������Yt���"�k��5(����m�G[b�)�������l��R�_�QC�oZ=h�C��,�k�D�\�G@�eU ����d���NE7����Kd%��h���D�9n������y?���N\�J����[,�P�0��v)P�3FKO�CM��z���,J�N���?Q�
�G�s_�� p�
�su8���>����u��������u�b~���i�P�!��[:��#m�Wn'�q����]zc�tMq�qR�`|���sr>Z��p�P��9���_T?��]�wd�M���4@q����`���q�����(���g@+��
���
�jl���6���c�-�o�mk�a�����:���w��:�2���@��d��~k�@��.N	��ZF+�Mo;�x{2_�X�>���:�����+lFU
YA�/�����,�=�}����;U��6�Q;r,L�wT��j/4�7h�������w�K�gY�5����J��[V7�������M���ub�J�������
����1���$��V���UR)��J=>����	�&��f#Sxw<��1%:�{�F��6��u�����S����t�r89��:��
��\�b4�01@��*0B�166�QN!d}��\��R���#K�tP���S!E���*2�?�}E?n6u.QAL����h<%iOG�k�-�.��e�Q,�r[N3m���p�q�R�5\Z�U�r�S)����)FX1[����B�&G��\kg�%�4;dEK���D�+�q�I3Mh%���h����t�z����q�%
�0����n'����^�P+c�+��������X'����J����B��kM���+�Y�w�q�eH�M
���/,TX�
��$��52�9UW�e��������sc���8Z�bZt+��h�]o��@>�x��|B���������Hm�"�p6*c�������f3�Q�MWq�����O��r��G�^�rT�E�^��1��
�aj���:��Qc�HI*��^v���0lg�m����2s�L
�)qo�ccx>#��b�CM���p�������r6#���F�����n��l�2��z��^Y���F�Y"b
��7D|/<�60v�=��6�G�������B��Yf�[�&�����\��G@$�l�8�� *����
��4h���/� ������;w�>-�����M��W����g�n�M���PG��y���������?w��g�J���J$�`7�]��~�m�[���N����w�oZ���A#1����wr|���n���b��U�����,�L����,��U�pM-�V�~	p�~����+��|�)�S+����jp�����l������*���_\$��l�Q�B7:�)N��K���a*�����a�����$�����ore����#N#�FK��Gc�v
F�����P��'
�gd�$i��������X����4�
��\]?�lhV*T�R�C��bl�e���gl�z^Z��Y�c`W�P�b>�z����8(e�{�un�
��5��d�@kQ+=l��nQG�G�)�,Qp�1��rg?������X^�N�*'c�����v;���hL�\����H���,z"&'���F�$���V��j��`8��H��l��..O����T�/x�d,����1>�B
F<�x��c��P�bEhv���c����2i�'�����.|�4�<�l��'�B�$fu<r��(�y>���n0<,��������-k��Ii�e��l�R��`i!]*�+N,�������������Ir��*:#���$�b���"���h�%wG�z#�A[H�A��^��6��kJlY���m;�1��!���,B�i��K������C:O�i�:���s��Q*���PE��"A8��fV��U'�����Qb������L��ig���������)K2���o����,�1�V�GS}2J��y�Flq=sshf�:?n��Q�5��(���lrgh�b+���XJ�[[���a�|�?���=���|(m�.��U��W1P(X�H�)��|�Xx�,��_�����o��xA�m��u�y39������NZ�{���>>M�.g(���83������k�>YHj�|<1�oC�N��ZP��I�(7���1��5:�������I����8c���zp6�ayq]G���d����`������h��`���CCd�cI�jl����;��u�VnohI���0�Z��K^�����������j���%!xG$-y��G8#:���Dn�Z�����i�J�?��&�Zb���2�'�D3
���`��2��^s�X�N]��(���+��e����KbnP;�D�z�i��S�^b����FF���b���s���;n� 7�F��l(e���]a3��-d���)�M�#-#�t�x��{0A���B����S%@���K�b��E������
�,^:9$.��OEf�*I�%VS*1I�j���R����RZ��'�q�ogf�o��f{&	y�T���4�����jQ�n�-AQ�KN�Q�]&6F��a�z����o��x�MVA��3��`g������3|�����eS}#C0�U�iW�������.�)��TY	�C��b9��R��Z|
�9�V�-T�(�b��+v�S�R�z]q����M�Gw�vD�������w���x����X����`������|J&94��/�%�������������%!k����_x/9���|A^�s#J.��[V�a�o ��<2���p4�R9��}��mm�~Sh����~��.�-�;����}���IK]�r_{a�h1���b�e���Q��gN�e�)����jhCQS�#���{��=�z=�q�����dY���W��J
�*r$72�A+9�`vgi����H��M/�lU�;�T�r�.�	
A�+4z����#�c�;�va����dq<��(>'��H*�f���x8h��3�ra7�K8,����cE�#��s6�*�����{�m�$��J���������1�c2���7����q�M���k�
�~�4��|�hG��0jb�N<B�im1�s�����ACv?��CtF�����o�w��x���GQ�T��Q�����)����F�����:���aA�:=�XT�BM��N9]"�
�[��C��:
$(x72�����j~E�pd��E���|xW��=1b������i�w����	Hi@�VR��4T�q)y�����=�[�����`�q���L�,g��6�Uzk ��dbf� o��|�V�d��m��kOUa$X�R�����/��~��d�iH��Z����/=z�K��O�U���S{v���~�!��%����1���f�Z[�����>Eu^8�F�?����
��D�W_F�:������Z~f���TGy�#���w������9D=��5S����#^�����q�i�c��V���}�a?�G���$�;gd��;?;�?�4�@|
�b�8[p�h2�(�M:��rF���*��H�K9z�K�Y��WC�jnE��dwh��uo�`e�
:mA�cvE5r��y�Rn����o��zI���-:|�>|����;,���i���
j�r��u�W���Jj�f���r��W��i[8G���w�a��v
+V.��%�If9J���Y$��;k�E�Hi
M�Nl
TB�
�K	U �K]��F�9^t)$��el��8l�L��Qlj�wERQ����3�J�A���r����]4���n���f�kl�z��-h��^C��3�1c��Z��62�6'�@r�H������&�+��=PP[u8�s����8)*���K7j�U��c���!z����T\�W�Bc���bFF��}�o5��86<��<�_��w����v���O�\v�C�8����.���a�Hn0�\(E��JG��a��d'��/o�>�*��M��b�
�*���R.l���$��B�.���J�j Q���nQ��u#=^0_4�@��R&7����������i�g��5�
��P�I�Rp`<�@���P����1S�EU�SJ�R�<�"1��YI���\����}�fZQ�x�N��rx���[�m<�4E��'A��6p�5����	������� Z��Vn����'�m�K��`�0�:l�F��F��t�~=�aP���OT��v���%���kw��u$��
/����Q7f	AE
Z�h���m���)���I=��O_�4,@�����O�
[�G8]<��(��TD�2����XI�X�7O-����&(_���(�D`� ���^-�&+��E��/<b��t�h0�E�QE��0����/���xg@�.G�/8�q$N&=D�����|%�/�J�e������c�!`����HZ�HVw0u��[���q��L�,���K���g��q�EI7�m��0b���W�|^�"y{��������Z����ob�vot���b����J���|G��_��.��n�X(~'u��,v^��;����\�>�����E`�H��\����� ��~�80��q�������c�q
�z��O�?!J(���G���<��{x�_*�
 M�
�*~���TE��
&�t�B�2��4������� _�+��gc�UP�R0�r*���J��_�+�@��W����F�<F6+���F+��[�X}�J9���a4*;Y.���u��7�P[U*��Gb������G6S9|�1�Sf��������,������������6�@�2Y��]^�3����gy!+����-��-���0^JzE&�d�a��]4i^�`6�,
JZ�
�T��6K��5I�K�������de�����!�(���1"F��fh���K�����������,�����vG39������o��k!����u4��G>��;�:��N�y��������-�tz��)���0c�lm�:�N�dl��
��[U�����m�3Fs���J����_���=�p�4����`��bb���V-=&��la�2��z�`������v�v�$�II�*�.��m��������3�[���H3�W�#���}�lR�������0�k`r�u����3���oC�
�iVL�hg�������dZ?u�N�N�!���[L����b
sxAq��l���C�4��o�1����[�|e�?�����^��}I����.f��[�9����%��������}���6�od��s�F�lc�.��@�}��8���Jf�,;p%PL���z�M!�;�(��o�a<fu0�(ka�`�:c<d�L;��AO+���~��G[��	G�Bb�C��J�X���U��!t��q_�P!M)���u����e#F�R���J�B)n��$KIO"t8�R���J)�
��7��Kj2�����`~�7�mqM&eN;
��~�PD�t*������~����7���9���I��6�r�
~_1k�Z��Z���F!�*�5&q'
�@N�2{�%�
�O��p�
'��Sy��{��l����t�k�o�!X�R����	N���������A��0XT����lZt2�XQx������3��L��>������y�x�L5�(F�F�<:y%7�����0j%��j���C!�:7����I8�G�1q@�@Bo:�����
��9�Cj'3Jz����{�;�m
�$]��zp�<�fP����yEU�L�C!t)�F�R�;����jH$��!��>$��QV�����73�v��0�>si����W+�k����w��x6�<PN���nx���r6�y�B-�����dY�;L�aa���0� �����m�3&#�T@F)�m6��H{}i��u�EdX���������"8��j}��N2���Hg0�4�W����9�t�(_=��5"G�������� ��/��~�/&w9
M�l�.���X���)*B�_����-��C�B:D��{���x{��1O�����������fR���4�����jq��a�U�8����4�����l(��7�/��E����������!�@��C"��w~D�����������EN&������XL�'�m��q��v`��������L�\��F�9p�E����}9y�Pf�(�s�����}>5?��y;�[�W��)+��C�I���
�cV�$�����S�oIs��-g�V���Q���^������B���'y�������@�Q�/����r������I�D��y>�@7����ntv�#}�@_m3�/�8����c�]�����'{�R����U�O;����c�������>Dp\���J����<��W����\��8��:��H`Zb����:�/��H�%
�i�1�g��hO�`Y��Q��FZ��&�i�i=q�p��8�
�|�GRF�j9%@���*(�R��%���_;�������*QM� XF��-���H��_�d�mv9 >�5��"��z����qyEi��%����F�3����a�����J].9dE�`���O/|��H�.{�a�1����!���K�����p8I��J}�yp?�(�:/��i���#{������t����N�����=���K`Q�c���!R��]�����qv�a���OXU�������m�� ������u�Mt�}R���K����qew�k��%��DV�G5,� T
Y��d'���������LN��*�<{�
��a@&�%�ja@^�A�t��43����`�y�7�Q������.��l-��;y��������������q"���g�qY�o����ul��4=*~�P��IV��-�����������:H5����?^�����SN�
O#J� ,E�-�.�!�,����Q.LQ��������z�E����j)��A+���������{�������������QG�c��
�{��8rn|nx�=
s��0��i����q���@�RJ�y�r}M?���-e��N0���`0�V��� n=���{�#�Z<�)n3+�2����c���JvoOi�����=�I_;������:��J9����mw���d"��8>�c9)��ZP�g���Gc�z!n�	�r����~��
t)�Y���r:���"�W�1w�����R�vb�R���j5��C�-J���n��	M��p�/��@�����p�3�D2���Zo�x�m;�j0?p���hI�L�H-����V�����mN�5��M�����F��u��t�T��J���q�"�&�Yh v+1�����Gz5��&|��,��	lz(I��c��\���.���U0y��X	�H�gh2��`���]���/�����<k�&���m�����3��$��nv�a&9��=�2E�B��}\�T)1��?�d3�e�5�2��{��[a��-����e,f�����G:Y+TChY��A��g�����_g��N.RJ��!bx������Z.z�H�c�>��,0��s�
������D1`��h+�>��@�������~�b,sym3���76+Abv&c
dKf��J�[R������-}�)�����J�� rPCej��TY4���{�QE�QK��k��ZQ�C���<�l���
9���Ii��,�/Y	'~N�R�Pr�S�&�P)�+@��e�o?�����>��-i�N����0k���\C�r�;�l��s?\�9:u�:���)�:Zt�����gv[�K�Xk����O[?�N{��Z���'�~���%Aq�"�/K^`")���{pM0�X���*��[(�G�ox���q0��N��q�Wyp��<������G��/���k�yFob��&|z���#|[���G����4��A�CR=L,�z[�GM��$�r��X�%Q��V,�p�+Q������_qNc� :y}�����5���+k�6U
]�-�������*k�/���Pd2�}��m<#��]���%l�Rqk+��@�\R�n��	�^9���u�������A���T_	�W�T_�-���u0\�7U!6T�nM��6�'j�OF��c?/�����
N�PTY�1A��Id���b�S{F�+%����p �|�\lCq[��MN-r���uW�*�2�+�T�P�<�fn[�c�J�*����\��������^���G��I���T�U#����[�j��!0��N���'�K�1��!QI.t���y�&�]]�d��!D�F�5?���KN���+|2�e2����6��z]���c=�$^���$�nc���Oz�&�d�Qj��4�Q���	Q1a;!*V6$Dx��VN�1��;���<-&�c���n����E�9��6���|V�_��
� ��W���*����0� {
����pN
I��m����K���l����
2�3�6��1�-t��������m����������y����@��_'2��y�����e��k���rRW�����w�{Y��y#�lz�+ ���:���9c�z���'����O^��t0�9��K�*�T�S�{�
�R>�gc�p:�c����I����[��07,^0s1K�m��.���_��J����&�>�/���8��p�%��y�):�&7	��``������:lI�~)E��`G��2���U�]
����P 6�����gE��)�hm��p�X<A:=��������1�E;���q�g���I��p3*�����6��������LALG]�	,\l�T�L2�1��_#���Fk�f��-�C��p��s��n)��(CY2�9�/c'��;w��M�Q7���|�i3�g�.h��_�g�?�m�&w{7��)�q6���GJt�Q��~����+�CoE9d���������#��'^����"[^��~=]�����e����3��zy3��d�N�3�(����J��E@���H����!d��8��$���b9�R�/��+*N��Yt���fb�q����9��������."��(�k^E�P�.�g<�NH(��TQI��!�O�5T���E%�'��������\���/H��)��UB��R�����8eb#�O����C%#n��\�9����N1?���#z"����?:�H+l�A��Xo�^$��������4��S*�d�\��p'�N\��5qf�O�[��
�3-�D�}���n�M�B�ke��aU�&jfdt���/0�t?����=@zoP:�h�u�2W|B�T2��|:]y��������E\9<����g�_����f��CY�w�"��]&�xx���t��z�������JP����Ki�{�U���=v+�����l�D���!1�h��`��+�Z���.���h������'Bno�7C��Z���J�?���z��m���+�����L��`�����orx������`������+���)&|iM�����\���O�|���V�\_��>���``���" D��������1&�}��������x�D����h�=�:�5�B�����7�(p��md��w�
���%B���*{�n(h��z*@��r	a�b|����	��fOw�&��,�HR)\�x�!����$$\�c��P�-8���x���f�5��'�2����7��5�d���^�9���OL|��a�l�X���*a^G�?0�2�8���d�J�����x�����x����e*>`����[�V�%L�"WJ�U�������>FI���)M����~N��1�5-��Wap�/���$�\G�L��Qd����iE5=`s4T^�s�����If�UY�^K���Z�����f��j
���(eX���
��]T�X��u���(����Z�oX�<=2��A�v��V��s��h����x �(�Yd�~����|�����i����3A#i��G�� ������I���j�a���~zC���2DH���)R]���U'[�&�5��J�����.�$��
W�����ax�k9
`��:��T�E��H�@
z����7N�h!����zQC+���O��v�O�"��w:8��M������
(�
]-��\UNXv���s��x���;�{�v�p;�L�S5���S����%Ss��fap'o������f�������(G@Z���������� ��<���R84Z�z
��nf%}P�(K�����{��
�F��������[����2�9:�p�4�0�+�C�n�4�=�iXS�`��$Vmo������b���C1
f����'b�>c�X�����L�^� +��i�c�7T��0
{�b��i����5��3
{�3
���4DC��i0��e��i�s�?�sh��w*���<�M�S5���,t�
����1�\<����x�Pii�/'#��9�x���b��b}X	am���������k 4�5S���������F��1�aW6��X���L�
��\4��~�yD��������|�-�
%�[��bt��i��
pXO��h��s��Xb�S[�v�P6��w���Q�)���,S�7Dn��`a�PM��|2��c�j�g`9�!����{���s��I��M'��C@��R	N�RB���'=�x`��S��p�u
�v�G�u�n��f���{Xd��"�s����YE�g_�����[�W��x@���f
?���\4�wJ��G-x3�pr��
^:�1�����tcJ:����n����c)�7�s�$$�B���[U]��	 ���XaI���W�^�v�Bh6|�TJ8b��V��{���v;�\���*I�Ok�qf��(����Yg��#������]u���5���e�m�Z�ZY�Xws?�::brI!����3����W��3��4��g5|�,�D}{�[��oS�������?�%�Rs��11B~���\�*P���q�31$Z����q�F�H���"sU��*,���K��������\;���)����_��x#���Q��iY�����(G��og�S7�������*����t��x��u1s&X��~)q���V}�(S���X�y��gm���(�m����z��!��r6lE�r���}�Q�u
e��P�n��^TMX�Y�T�i�C9L�u9NU�C�x�����7��=���ue��0EY� ���pp���3�F�=��>����>T+R'_����\����s�_��������Y��kuN#�{Z}%�A<�.���c�GML���J�!�%����:��X`}�'�����-����]-a�j�{��M3�K�x�G7�l��Z4����lM�s/,�`�F���^���U+��MM1�&�"��/Q����R�-�m�D�����'������}e��$iP6�J��?�OS���R���������N'��t)~]�tj68��m#]�5r���k���X�ttV��a�b�78�P��[��Z��V�S��G�e��2����"_%����{#��1������5_�Ip��c��_��yv�<KD����r�&O�a&��u��1Y�.����L�}��oz�|���w�����?7nD���F��20x�>�5����
0�Y���3Cx�
�t��KCx���{	���y�,��'��C��%�W�����D�[�({��C	��_�_[�j���3�C���Ej�"y��Qd~�I��/�[?�}��s�UV���j�G�����t��g���'�A����b�����e-�9OU����x_�kW�'�����.~�V�g�N���$^��Px���@d��������[����(�p��o�|����0���i�(��������i��W���
MH9�
Z-d��z���+����d_It���o�A~��
����!m���m���1�|=�og�l��V����<9i�_�'�NK��y���4������f�������[�������97����I����n��W-��4yL���V���w{-*y�n�������O��i�w���}�i���P��
��v��P�NK!|=�-��m���O��Q����&��C�6j���yX]�A����s���u��S��7��}�g��k����O{��S���hn�gG�������[�vr��nK\}i��n�z��������%��R���m�F��6J���1
/�o�;���	L��V���N����`��x�����V�+�04�wm	��u�:>i�����V0�,���*�����<�l>�p}
`��FS�2�d0��y�r����v��Q06��������M^f���~<o����@�6���5�L�������(}��L���.?���[�)�k:{���O�|iT�n��j���z����ZR��.�0_{���0�@
�rww���>��>�\�!X��h��Q*`�����6��h��?��s���)�oG0a��������f���.^��fG��JT�\�������u�����CxiX���	pA]�'���c+����@��<�T0
����Rs���(U��)�#P
M�
�b1�
��(���j\������C���P��
�6 �W���S]�6����D���8�63 ���-���������]	i��C���c-��d�h3�a|��V����n���c�px4�'�]�F�\DV���MV@y���Y`�5b���r��y�������%v�1�� H�&)��#�w�2J���������JncxHK��#�G�C���4��������pq�-������!�Z?7�XqrD�j��[��#��g�<�� �	�O�4V�U���������A���F�����R:bV�����U<�0��0J���E���)&�9�P���o�����!g�������bn��
X��a��W��U��l����H�+�G0��F�-4�����Go�>63G�O���,���C�
u�-�3]�q*��<',�*��d�>�d���(6�W����^387�D��!���f�!�A�8$������{'��"Y�����c�c�t�-H�z:bs��y�I����}d��U]1_���al�/|Q�_���vY1_��?�[��o�K�����GNO�p��E�+5�J�I����Pm�������>�X@��a
�'�Z����v��|F�E����:�g�A����(R&�X���Kh�Z*��@��]p�E��^>�N�.�";��]vQQU�����uS�U
=>����fW,v�	og���������~��{������R3_�;��	�{�d3�}�����?��p��)�*���b�9Ed�-��d!Y��Q����@%{�a�	�5x���.��P������sb������j)n��Y�hao��\���\�<���=���|�r��q���&��a9�N\��t;�k������Mwu�y�:J�4���w�Y@����*p��c��:�����������d3Y�$7��N{����������,�2�[O��]|,b�h:����l�WvB�O�JC�*
Y���j"�;�����PK
��}�u��:�������72��z�>2����K�u'�Ey���o�S�LZ�R8VVz}��3DdSYoS�;��FD����_�e&�{*Wh������[��7���>#�	�v�a��q3q�>
�6L.��y>L���QF���9�(�I��a ��t��h�T5D�R���4*�L�i&9}�	���Z�	WhxG.l����y
v�p��j����<Q���9����/������N�c�8�t���7gJ�F-nx�������5�9Li-�N�]WJ BS�<8|T��Q_*�������/~@.5��j��89�D�����$�08Aqe�����Sl25�<�M`Y�0��LYfK_�4���������Y����F�L*��UW����c���j<��#��c%���������Z��I���:�1��=D����Xw~���x*H�����>�s�S���g�C��������3��<R(�l](<X�Fo�X�{a��Gw|)�f�o*>�.� �dz�1���\�y�&q�]�`���|��T0suU�:����*F��_$��Xv�,�1Kt��6�2��������/�r�A�_���G����y�^�2�K��M&��y���w��;��G�����J��Q����:�#c���P(�f������l�O�@�q�{��;�Z��P�����%F`� `�������|��h�o����9Yn��l���X��J��kF�GF����c]����Q��+U���b�+�_c�����o�����0�)��y�h����df��n�S�y@S6��{��{�F=?��E�IN���sTd}~1�� ��!�G[m	L:�
'���B������k#.iF���nM��Rx��r�`	�G+b-�X"�q(pl���}L���a�L���?���G��u$�)&��e�*�"U�fe�^�{�z����66+�2��Tb,e@z.��VB�(d�x�Rr*��Q�{��moG������/�E��b�u����Q��Ir�����y���Z��=���~
S%�~�y�0Zufu1������;�;*�������,�%�
v��i�i�����-�[�3�p�:�m������ZN�4���������k/g�HW������3���+���-����{��G�7�mh.��B/X8��#��T/���q��{�����|�C�B�)���%�O�$r��U�+JE5���g%�u(��P�s�b4�����Y��S�'���]�n;��r2����������)�Zl&i�LU&��)co�p0�}�	Y����^lR���kn��w�������p"Y����D�b%�����w������,��'�b��l��@����[v�H&/�Td[K)��s%�����#Q��4�l/@�{1�����6�::s8����X�_��!���
�������1l���`	��s�^F�J�L��I��^��x��A0���p<���9��������W�g���������]L�	����N�8���'{���#S-��~>�\��Q!�"���l����J��w�O�������|���4����)f��:w7�6�r�1�F�9�3�����D��9^~�7O5:�|I�K���
�5�.�k����n�t�)���YUIo��F�X�+7/�����e3�?o0M��+a���Q*?�����?��"�>e5������ ��5���3��;8��Nn�4�Q�/�E�.������f�_&�47���O�
{�����������x�t���1e�^���C�.a��������OO����3L�+�p���W�=���8�_0N����uT�/���/�{�����)F�g������b/bR}Q����K����Z���?7:^�p��C��d9�0q�0�a�'�N��FP�0R7�Q�\��b,���&�,f���n��>���V�����`������(��s">�S�����d������`��b0|����t�������Nk]�rZUx~<r��?u���|\)G(S�D��1]i�����"����'q���Qw-�e���
�Y����A9�[�0�6�G�	�7��<xw-@,��4�>]��H�7��N�xp�=�V�'&7_Bo��F�RV���V���g-��p0:t��I�M����Y_bu���|�������s����{"���xn4C6K�[[�F1/z����J�����17eX^�R����a[e������������(>Qs���V1�������EZ(�KIi�%c�N������-S	��e�Hm���7[N�z�T���JE���~s�k���g�b���0m��c\:�
W����i��j�'~:���t1��s��t[N��j9�mt��w�L������.�	�X!+�-��Z����������5[wL_�t#��0�I���"%
��~hy��"����������������h��/R��;;�P(�+RY���c���7:k���^s����5b
����db�d G���>\^%s:���#e4i�_��(�n/�y��)qY�(�U,]�P�\�\���R�?�1�	�����f����I���?��j�g��o�O~���W.W�b�R���f������+��C�a0�c 3L�f�_���6������t�N�{w���,2��BSkg����"1��]����x6q{�3��l0��!��+���"������w���d�A����y��fp�Pr;T���y�=x�Mc�v.U���N�	��c����,%)FZ��W�����������n]��g��5�w��t>&p���P�%�E*�"��ER�A�j�����B�����+��Rf(�� �xq�@ �CB%6������.���C'���8A"�*���mEz�����O�#'+y��FLBJ��g��r������V�U����F�J���e(�Hb9������;Nn�?��"T��?-����=iB�CT��}��F�tVL����(Z)�V�E;�j�p�A�IC�����-�,�H�Et��]�"���P�c��&��-K�A�<0u�|8�:��R0����,U��z����@��)���)�98au;d����U0HH%
�q@�-�_,�2��`<]��elH_���j6Gp"��~hJ��a�#��8*�XLy�#�d88����W�A1�}�4���������&��(����`���F�m���6*�d%�1���.���p|�I�-�#�,�'|�4G�c2�8`��.�i�\�t���k�(Lx����.
�G��r�K��H6��mW����eWw����c�{mu�E0�E��>�H��w�hb��d	+�/�bIV��M���/?�;G���?$<�vJv��SL����u�
��}���K������fo!^a:����:��b�J��5U�~o��������F���{�������dMU���t|
���t_��+���o)O�����[Th��:��u�C����Eh�����2A��v�`�I�%Uy�aa/ 7����F��^���)��<�f�q�wW�����d/�����
W*zp�q=LM�����o(�-{�|k��DVc��o$u����>�����V��2tB2n����PH��t��M��h�7g<�F}��i/Y������sHW��Nku<y�����QJ�����%�u�W�3X��-e�,*�#)�K�{O@I������*�Pbm
�4��%��r<�������P�\-�e��Tw?��r��3���q���������7]������M�n@B,e6���-z.��7>��|���U���f��{����f�����O���-�
�Q�b<��X_	�L@�!+������XG~k��Kn=C�*�C���d(������F�����?�+�og�p�g^��/�D�~�K�b��F�!rH,H,$*���
��/N��;#�
������6?A��.\c
\��WN�)*zUS���V��Y����pcJ�c�T{��I�S���|���oH�E]� �����z^�b
���_�����|����������d�aI�V���ZM�$��}b��CtC�����4�`�J���]��o��k��J����i�����a/G�������YD��s\GY���u��$j��_�k4�6�����j����@!�H����]��'�d��	��
k����b����d��<�b�5��>�j�������e�����G?��[wv�^t��\���?��m�2����tGN���e[�����@��)��-q=�Q_QS����a��|��m���G]l���\X�Z������9&si�2�����l���5\����Jc��=����
��?�1a1D>�|���F3*��[)/��g���������ct)��%�e�W6���C%uQ��Bi�C��j�|��r�)��1��_�7,��Z)�Y_G������
%
5�j�`8�C������%��;������f{����+���.��6������LP����A��l���4A�u�xM8N=��ivH���.��`91�l�������{���#�U<m�X�{�9~�������<x��������g��B�C��d.�a�*���)��6��\g�����.?(���zP�w�����AM=�����B�:(�z���.^+xY2r�jQE=�����=g��a�P:�Fc��`�R�$�+��a��
D��.\Ns0�]|��
t���R�n��_u	*��K�|���CU�i��=���fe�_���e<(�����r%��p��JM�I{�Q��O�Kt���x��`
���/�����������!Y_��bd&JYL
��-�
�����8��bI�	��~^7�`^�����o�����a.a�h�c�D��B�,�;/_���y��g�<eA@���X�9��8����y�X�c{1�v�!I��7s��|	#�FP�{��P�<�X#��bM�F2��x,<�$xb���>�$�D�9��k���O��U'r��-zcbv�<}�-�=������-�����~���j��7uc�%�o6���a(c_�i,x�u��� �����6\�UH�rzc-�z	/.�����HU�|N-�bg������o5��d�A�m�R���L������|y1���8���f�������B����x�A_�b7�:����4�����f|�}7�~�-��mD����}6�����U����fG������m��h_���:��/�=u����/p����6v�*�����Q�?+�e����H�H-��U�t��/��U�t~��
���v�+�{q������k����E_
D��m�bx�A����4k
���	�$ eJtrq gz�%���	j\Y���U���|{������G��X�4c}���K/3.K�
��uo��ot�a��Wi��1�������>�L��:E���T����;
�IB-�j�����\U��J����<��I�3>_����B�z_��rT���"�:���	C�������eq�>%~�q�Sf�x3��>�<�����q��_����gp�y6�
I7�Sd�2�M|�
o
�t��Jvz�{���t�7���q��:��S���v�c�X_����5t���Vm���}m	���3��r���t}Cbi�!��������|G�z��{��!r"���_��w{]g��Q��{������t;��K�
�qA���;��@�IaQt�@�������G=��I����g���z������f�1�l�k�l���f������(umc��o�CYs#��?~H�Y�$dYC��K���u��Q��
��� ��hD��X�r�����@w4F�U��)��6k�,�lPU��N�����J�x��i"�^��Z \[�����~�XJ^���3x��Y��I��1����7am������>S7������c3[�71���VrM_�i|�`<��f��y�Bc#^v�A��^���$�0��X�����+��R��c�t
&ZU���� �����[CW8���{�A����S�!�5
�pe��B�) +��tN�����Zo�O_%v��G�"�@�F��i�w��Re#�ze}�����L7l����K��F#�nZr�rX��>9��y���BZr������e�^-�X�%M�n�X�`0�g����`.V4�M������Y���Y���X�R��R�J�W�����K�����H'���>�pwa������8J��?�~�6+n���H�4�"z�D��oM���h���Ii�N$TI���tSt*n�NE�N�����e�@��h70�@���{�b����Y\����XS�5���6��~��SKi�������������|���d���Q�����}��h�6:CP�d��m��%�mda���=*i�E/0�{�(q���X������b 4`��<P����c�Y�F_Y�R��Ao:?P�7�
j}���6�u����&���V���	<���|����	���z���y���IQ)��0"�f�"�6���wX���H)�^�ak7����&^�c]
��r������Y����Af,	����o�Y���_�����nKT�=R���3w�Y;=�4>�������t����|�����7���9����2�i�������8��2zY�������^w+�R��^�^�2�,���~��-U��a�A`����>0Zp�������	}���}[�/�}L�gV������V�V�*:��.�#��./$���#\^^�aT�{~D�<_x�w���_�E?���}��pbl��V�M��H���_jH��������D?�����o7h�4��������Y��-����~_�)�J������s���p�#����������v\�U��z��.�]����z������}��������>��������T=F�� >,2v&�1�L�	��#��Y3]��S�0
k�t�����h���E�w��I��|2�u������i��?�3V)\a4hP�dMu��3l�������h�����dp����	�������d\��6`�����@���&kQ#Y���
��30cx0
o��]���B�81u�(�X�����@��1�V���fh����Iu�N�r}���H���c�
Q���p6�?��� ��_p����u��#�\������T�d���������Kt�����o�+�5|�i���������}�)��6�(��3���rr_�=�������g<o������������W������V��bkl��)��?��#�� ��� '������V�_�u?���c���J����{^��
#25Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#24)

On Fri, Feb 15, 2013 at 8:01 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions. If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious. Where it gets a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated. Then you dump. mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable. As the patch currently stands, you
get an error on the attempt to REFRESH mvb. I think that's a good
thing, but I'm open to arguments to the contrary.

Hmm, anything that means a dump-and-restore can fail seems like a bad
thing to me. There's nothing outrageous about that scenario. It's
arguable what state dump-and-restore should leave the new database in,
but I don't see why it shouldn't work. I predict we'll end up with
unhappy users if we leave it like this.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#26Bruce Momjian
bruce@momjian.us
In reply to: Robert Haas (#25)

On Fri, Feb 15, 2013 at 08:24:16PM -0500, Robert Haas wrote:

On Fri, Feb 15, 2013 at 8:01 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions. If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious. Where it gets a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated. Then you dump. mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable. As the patch currently stands, you
get an error on the attempt to REFRESH mvb. I think that's a good
thing, but I'm open to arguments to the contrary.

Hmm, anything that means a dump-and-restore can fail seems like a bad
thing to me. There's nothing outrageous about that scenario. It's
arguable what state dump-and-restore should leave the new database in,
but I don't see why it shouldn't work. I predict we'll end up with
unhappy users if we leave it like this.

pg_upgrade is going to fail on that pg_restore error. :-(

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Kevin Grittner
kgrittn@ymail.com
In reply to: Robert Haas (#25)

Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 15, 2013 at 8:01 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions.  If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious.  Where it gets  a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated.  Then you dump.  mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable.  As the patch currently stands, you
get an error on the attempt to REFRESH mvb.  I think that's a good
thing, but I'm open to arguments to the contrary.

Hmm, anything that means a dump-and-restore can fail seems like a bad
thing to me.  There's nothing outrageous about that scenario.  It's
arguable what state dump-and-restore should leave the new database in,
but I don't see why it shouldn't work.  I predict we'll end up with
unhappy users if we leave it like this.

Keeping in mind that mva may take hours to refresh, and mvb may
take only minutes once you have the data from mva, what behavior do
you think is preferable?

The alternatives I can think of are:

(1)  Force mva to refresh on restore, even though it was empty and
not scannable on dump.  This may delay completion of the restore
for an extended time.  It would leave both mva and mvb populated.

(2)  Populate mvb by using mva's query as a regular view.  This
would leave things in the same state as they were on dump, and
might possibly optimized to something faster than generating mva
and then mvb; but probably would not be much faster in most cases.

(3)  Change the failure to generate data for mvb in this case as a
WARNING rather than an ERROR.

(4)  Actually dump and restore data with COPY statements for
materialized views, rather than having the dump create REFRESH
statements.  The main down side of this, it seems to me, is that it
opens up materialized views to direct tinkering of contents via SQL
statements, which I was hoping to avoid.  Perhaps this can be
mitigated in some way.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Kevin Grittner
kgrittn@ymail.com
In reply to: Bruce Momjian (#26)

Bruce Momjian <bruce@momjian.us> wrote:

On Fri, Feb 15, 2013 at 08:24:16PM -0500, Robert Haas wrote:

On Fri, Feb 15, 2013 at 8:01 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions.  If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious.  Where it gets  a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated.  Then you dump.  mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable.  As the patch currently stands, you
get an error on the attempt to REFRESH mvb.  I think that's a good
thing, but I'm open to arguments to the contrary.

Hmm, anything that means a dump-and-restore can fail seems like a bad
thing to me.  There's nothing outrageous about that scenario.  It's
arguable what state dump-and-restore should leave the new database in,
but I don't see why it shouldn't work.  I predict we'll end up with
unhappy users if we leave it like this.

pg_upgrade is going to fail on that pg_restore error.  :-(

With the hard link option it should succeed, I would think.  If we
arranged for the check option, when run without the hard link
option, to report such cases so that the user could choose to
either truncate mvb or refresh mva before the upgrade, would that
satisfy this concern?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#27)

On Sat, Feb 16, 2013 at 09:53:14AM -0800, Kevin Grittner wrote:

Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 15, 2013 at 8:01 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

There is one odd aspect to pg_dump, but I think the way it is
behaving is the best way to handle it, although I invite other
opinions.� If you load from pg_dump output, it will try to
populated materialized views which were populated on dump, and
leave the ones which were not scannable because the contents had
not been generated in an empty and unscannable state on restore.
That much seems pretty obvious.� Where it gets� a little tricky is
if mva is generated with data, and mvb is generated based on mva.
Then mva is truncated.� Then you dump.� mvb was populated at the
time of the dump, but its contents can't be regenerated on restore
because mva is not scannable.� As the patch currently stands, you
get an error on the attempt to REFRESH mvb.� I think that's a good
thing, but I'm open to arguments to the contrary.

Hmm, anything that means a dump-and-restore can fail seems like a bad
thing to me.� There's nothing outrageous about that scenario.� It's
arguable what state dump-and-restore should leave the new database in,
but I don't see why it shouldn't work.� I predict we'll end up with
unhappy users if we leave it like this.

I agree that making the dump fail on this account is bad.

Keeping in mind that mva may take hours to refresh, and mvb may
take only minutes once you have the data from mva, what behavior do
you think is preferable?

The alternatives I can think of are:

(1)� Force mva to refresh on restore, even though it was empty and
not scannable on dump.� This may delay completion of the restore
for an extended time.� It would leave both mva and mvb populated.

This is reasonable. If the user doesn't like it, he can presumably use an
edited dump list to remove the REFRESHes.

(2)� Populate mvb by using mva's query as a regular view.� This
would leave things in the same state as they were on dump, and
might possibly optimized to something faster than generating mva
and then mvb; but probably would not be much faster in most cases.

Interesting idea, but I don't think adding novel server behavior is justified
to achieve this.

(3)� Change the failure to generate data for mvb in this case as a
WARNING rather than an ERROR.

This is also fair. However, I think it's better to restore more valid MVs
(option 1) than fewer.

(4)� Actually dump and restore data with COPY statements for
materialized views, rather than having the dump create REFRESH
statements.� The main down side of this, it seems to me, is that it
opens up materialized views to direct tinkering of contents via SQL
statements, which I was hoping to avoid.� Perhaps this can be
mitigated in some way.

This is a door better left closed.

Overall, I recommend option 1. None of the options will furnish the desire of
every database, but the DBA can always tailor the outcome by replacing the
dumped REFRESH statements with his own. I'm not envisioning that MVs left
invalid for the long term will be a typical thing, anyway.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Kevin Grittner
kgrittn@ymail.com
In reply to: Noah Misch (#29)

Noah Misch <noah@leadboat.com> wrote:

On Sat, Feb 16, 2013 at 09:53:14AM -0800, Kevin Grittner wrote:

I agree that making the dump fail on this account is bad.

I would argue that this is an overstatement of the issue except for
restores that use the single-transaction switch and pg_upgrade
without the hard link option.  In all other cases, one could just
issue REFRESH statements after the dump successfully completed all
the other work.  But those two cases are important enough that the
issue must be seriously considered.

(1)  Force mva to refresh on restore, even though it was empty
and not scannable on dump.  This may delay completion of the
restore for an extended time.  It would leave both mva and mvb
populated.

This is reasonable.  If the user doesn't like it, he can
presumably use an edited dump list to remove the REFRESHes.

Overall, I recommend option 1.

I'm OK with that approach, and in the absence of anyone pushing for
another direction, will make that change to pg_dump.  I'm thinking
I would only do this for materialized views which were not
scannable, but which cause REFRESH failures on other materialized
views if not refreshed first (recursively evaluated), rather than
just automatically refreshing all MVs on restore.  The reason this
seems important is that some MVs may take a long time to refresh,
and a user might want a dump/restore to get to a point where they
can use the rest of the database while building the contents of
some MVs in the background or during off hours.

None of the options will furnish the desire of every database,

Agreed.

but the DBA can always tailor the outcome by replacing the dumped
REFRESH statements with his own.

... or by issuing TRUNCATE or REFRESH statements before the dump to
avoid the issue.

I'm not envisioning that MVs left invalid for the long term will
be a typical thing, anyway.

Agreed.  I think this will be an infrequent issue caused by unusual
user actions; but it would be bound to come up occasionally.

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#31Thom Brown
thom@linux.com
In reply to: Kevin Grittner (#24)

On 16 February 2013 01:01, Kevin Grittner <kgrittn@ymail.com> wrote:

Unless something else comes up in review or I get feedback to the
contrary I plan to deal with the above-mentioned issues and commit
this within a week or two.

At the moment it's not possible to rename a column without using ALTER
TABLE on an MV.

Also, shouldn't we have the ability to set the collation on a column of the MV?

And the inconsistency between regular views and MVs is still present,
where MVs always dump with column parameters in their definition, and
views never do. Not a show-stopper, but curious nonetheless.

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Kevin Grittner (#30)

Kevin Grittner escribió:

I'm OK with that approach, and in the absence of anyone pushing for
another direction, will make that change to pg_dump.  I'm thinking
I would only do this for materialized views which were not
scannable, but which cause REFRESH failures on other materialized
views if not refreshed first (recursively evaluated), rather than
just automatically refreshing all MVs on restore.  The reason this
seems important is that some MVs may take a long time to refresh,
and a user might want a dump/restore to get to a point where they
can use the rest of the database while building the contents of
some MVs in the background or during off hours.

Maybe it would be a good idea to try to put such commands at the very
end of the dump, if possible.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Kevin Grittner
kgrittn@ymail.com
In reply to: Alvaro Herrera (#32)

Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Kevin Grittner escribió:

I'm OK with that approach, and in the absence of anyone pushing
for another direction, will make that change to pg_dump.  I'm
thinking I would only do this for materialized views which were
not scannable, but which cause REFRESH failures on other
materialized views if not refreshed first (recursively
evaluated), rather than just automatically refreshing all MVs on
restore.  The reason this seems important is that some MVs may
take a long time to refresh, and a user might want a
dump/restore to get to a point where they can use the rest of
the database while building the contents of some MVs in the
background or during off hours.

Maybe it would be a good idea to try to put such commands at the
very end of the dump, if possible.

Here is the dump order as currently implemented in that patch.  MVs
are created at the same priority as tables and views.  MV REFRESH
and MV index builds obviously need to follow population of table
data. These are at the same priority because it makes the most
sense to populated an MV without any indexes and then build them
before the MV is used to populate some other MV.  Dependency
information is used to get that to sort properly within the
priority level.

    1,                            /* DO_NAMESPACE */
    2,                            /* DO_PROCLANG */
    3,                            /* DO_COLLATION */
    4,                            /* DO_EXTENSION */
    5,                            /* DO_TYPE */
    5,                            /* DO_SHELL_TYPE */
    6,                            /* DO_FUNC */
    7,                            /* DO_AGG */
    8,                            /* DO_OPERATOR */
    9,                            /* DO_OPCLASS */
    9,                            /* DO_OPFAMILY */
    10,                            /* DO_CAST */
    11,                            /* DO_CONVERSION */
    12,                            /* DO_TSPARSER */
    13,                            /* DO_TSTEMPLATE */
    14,                            /* DO_TSDICT */
    15,                            /* DO_TSCONFIG */
    16,                            /* DO_FDW */
    17,                            /* DO_FOREIGN_SERVER */
    18,                            /* DO_TABLE */
    19,                            /* DO_DUMMY_TYPE */
    20,                            /* DO_ATTRDEF */
    21,                            /* DO_BLOB */
    22,                            /* DO_PRE_DATA_BOUNDARY */
    23,                            /* DO_TABLE_DATA */
    24,                            /* DO_BLOB_DATA */
    25,                            /* DO_POST_DATA_BOUNDARY */
    26,                            /* DO_CONSTRAINT */
    27,                            /* DO_INDEX */
    28,                            /* DO_REFRESH_MATVIEW */
    28                             /* DO_MATVIEW_INDEX */
    29,                            /* DO_RULE */
    30,                            /* DO_TRIGGER */
    31,                            /* DO_FK_CONSTRAINT */
    32,                            /* DO_DEFAULT_ACL */
    33,                            /* DO_EVENT_TRIGGER */

I don't think that pushing MV refreshes and index creation farther
down the list should require anything beyond adjusting the priority
numbers.  I don't see a problem pushing them to the end.  Does
anyone else see anything past priority 28 that MV population should
*not* follow?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Kevin Grittner
kgrittn@ymail.com
In reply to: Thom Brown (#31)

Thom Brown <thom@linux.com> wrote:

On 16 February 2013 01:01, Kevin Grittner <kgrittn@ymail.com> wrote:

Unless something else comes up in review or I get feedback to
the contrary I plan to deal with the above-mentioned issues and
commit this within a week or two.

At the moment it's not possible to rename a column without using
ALTER TABLE on an MV.

Also, shouldn't we have the ability to set the collation on a
column of the MV?

Will fix.

And the inconsistency between regular views and MVs is still
present, where MVs always dump with column parameters in their
definition, and views never do.  Not a show-stopper, but curious
nonetheless.

I haven't worried about this because current behavior generates
correct results -- this seems like a micro-optimization.  The
explanation for why it wound up that way is that creating a
materialized view is in many ways more like creating a table than
like creating a view -- it seemed safer and less invasive to modify
the CREATE TABLE code than the CREATE VIEW code, and specifying
column names just fell out of that as part of the minimal change.
In looking at the pg_dump output, though, I see that the CMV AS
clause also is getting the names right with column-level aliases,
so it should be pretty simple and safe to leave off the
column-list section for MVs.  I guess it's worth it just to
forestall further questions on the topic.

Thanks!

-Kevin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Noah Misch
noah@leadboat.com
In reply to: Kevin Grittner (#33)

On Mon, Feb 18, 2013 at 06:49:14AM -0800, Kevin Grittner wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Maybe it would be a good idea to try to put such commands at the
very end of the dump, if possible.

��� 25,��������������������������� /* DO_POST_DATA_BOUNDARY */
��� 26,��������������������������� /* DO_CONSTRAINT */
��� 27,��������������������������� /* DO_INDEX */
��� 28,��������������������������� /* DO_REFRESH_MATVIEW */
��� 28���������������������������� /* DO_MATVIEW_INDEX */
��� 29,��������������������������� /* DO_RULE */
��� 30,��������������������������� /* DO_TRIGGER */
��� 31,��������������������������� /* DO_FK_CONSTRAINT */
��� 32,��������������������������� /* DO_DEFAULT_ACL */
��� 33,��������������������������� /* DO_EVENT_TRIGGER */

I don't think that pushing MV refreshes and index creation farther
down the list should require anything beyond adjusting the priority
numbers.� I don't see a problem pushing them to the end.� Does
anyone else see anything past priority 28 that MV population should
*not* follow?

DO_EVENT_TRIGGER should remain last; it may change the behavior of nearly any
other command.

Moving DO_REFRESH_MATVIEW past DO_TRIGGER would affect the outcome when the MV
calls functions that ultimately trip triggers or rules. Currently, the
behavior will be the same as for CHECK constraints: the rules and triggers
don't exist yet. This may also affect, for the better, MVs referencing views
that need the CREATE TABLE ... CREATE RULE _RETURN restoration pathway. It
looks like a positive change. On the flip side, I wonder if there's some case
I'm not considering where it's important to delay restoring rules and/or
triggers until after restoring objects for which restoration can entail calls
to arbitrary user functions.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Kevin Grittner
kgrittn@ymail.com
In reply to: Noah Misch (#35)

Noah Misch <noah@leadboat.com> wrote:

On Mon, Feb 18, 2013 at 06:49:14AM -0800, Kevin Grittner wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Maybe it would be a good idea to try to put such commands at
the very end of the dump, if possible.

     25,                            /* DO_POST_DATA_BOUNDARY */
     26,                            /* DO_CONSTRAINT */
     27,                            /* DO_INDEX */
     28,                            /* DO_REFRESH_MATVIEW */
     28                             /* DO_MATVIEW_INDEX */
     29,                            /* DO_RULE */
     30,                            /* DO_TRIGGER */
     31,                            /* DO_FK_CONSTRAINT */
     32,                            /* DO_DEFAULT_ACL */
     33,                            /* DO_EVENT_TRIGGER */

I don't think that pushing MV refreshes and index creation
farther down the list should require anything beyond adjusting
the priority numbers.  I don't see a problem pushing them to the
end.  Does anyone else see anything past priority 28 that MV
population should *not* follow?

DO_EVENT_TRIGGER should remain last; it may change the behavior
of nearly any other command.

Moving DO_REFRESH_MATVIEW past DO_TRIGGER would affect the
outcome when the MV calls functions that ultimately trip triggers
or rules. Currently, the behavior will be the same as for CHECK
constraints: the rules and triggers don't exist yet.  This may
also affect, for the better, MVs referencing views that need the
CREATE TABLE ... CREATE RULE _RETURN restoration pathway.  It
looks like a positive change.  On the flip side, I wonder if
there's some case I'm not considering where it's important to
delay restoring rules and/or triggers until after restoring
objects for which restoration can entail calls to arbitrary user
functions.

I didn't quite follow all of Noah's points or their implications,
so we chatted off-list.  He made a couple additional observations
which allow some simplification of the patch, and allow MV REFRESH
to be moved to the very end of the priority list without ill
effect.

(1)  While it might be incorrect for the CREATE INDEX on a
materialized view to come after event triggers are set up, REFRESH
can be expected to be a routine action in the presence of such
triggers, and it might actually be incorrect to REFRESH when the
triggers are not present.

(2)  REFRESH MATERIALIZED VIEW creates and builds a new heap, and
reindexes it after the data has been loaded, so the timing of the
CREATE INDEX statements on MVs is not critical, as long as they are
done after the CREATE and before the REFRESH.  We could drop them
into the same priority as the other CREATE INDEX statements, and it
would not be a big deal because the MVs would be empty.

This should allow me to simplify the code a little bit and move the
RMV step to the very end.  That may have some advantages when users
want to start using the database while MVs are being populated.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#36)

On Mon, Feb 18, 2013 at 4:48 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

This should allow me to simplify the code a little bit and move the
RMV step to the very end. That may have some advantages when users
want to start using the database while MVs are being populated.

In the department of crazy ideas, what about having pg_dump NEVER
refresh ANY materialized views?

It's true that the job of pg_dump and pg_restore is to put the new
database in the same state that the old database was in, but I think
you could make a reasonable case that materialized views ought to be
an exception. After all, even with all of this infrastructure,
chances are pretty good that the new MV contents won't end up being
the same as the old MV contents on the old server - because the old
MVs could easily have been stale. So why not just get the restore
over with as fast as possible, and then let the user refresh the MVs
that they think need refreshing (perhaps after getting the portions of
their system that don't rely on MVs up and running)?

At the very least, I think we ought to have an option for this
behavior. But the more I think about it, the more I think maybe it
ought to be the default.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Robert Haas (#37)

2013/2/19 Robert Haas <robertmhaas@gmail.com>:

In the department of crazy ideas, what about having pg_dump NEVER
refresh ANY materialized views?

It's true that the job of pg_dump and pg_restore is to put the new
database in the same state that the old database was in, but I think
you could make a reasonable case that materialized views ought to be
an exception. After all, even with all of this infrastructure,
chances are pretty good that the new MV contents won't end up being
the same as the old MV contents on the old server - because the old
MVs could easily have been stale. So why not just get the restore
over with as fast as possible, and then let the user refresh the MVs
that they think need refreshing (perhaps after getting the portions of
their system that don't rely on MVs up and running)?

At the very least, I think we ought to have an option for this
behavior. But the more I think about it, the more I think maybe it
ought to be the default.

+1 from me from a minimalist point of view.

I think of a matview of the manually refreshed kind as “can contain
stale contents (or be invalid) unless someone manually makes sure it
is up to date (or valid)”. Making any matviews invalid by default upon
restoring (itself being a manual action) would be consistent with that
definition. Additionally, ISTM to be the least arbitrary (and hence
most elegant) choice, and even more so in the context of
matviews-depending-on-matviews.

Spamming some more craziness:

Another (more elaborate) suggestion could be: Store for each matview
whether it is to be rebuilt upon restore or not. Using this setting
would intuitively mean something like “I consider this matview being
valid a precondition for considering the database state valid.”
Setting this to true for a matview would only be allowed when any
other matviews on which it depends also have this setting set to true.

Just my €0.02 of course.

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#37)

On 2/19/13 8:54 AM, Robert Haas wrote:

In the department of crazy ideas, what about having pg_dump NEVER
refresh ANY materialized views?

It might be useful to have an option for this, but I don't think it
should be the default. The default should be that the new database is
"ready to go".

Then again, when would you ever actually use that option?

This might be different if there were a command to refresh all
materialized views, because you don't want to have to go around and type
separate commands 47 times after a restore.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Erik Rijkers
er@xs4all.nl
In reply to: Kevin Grittner (#24)
1 attachment(s)

On Sat, February 16, 2013 02:01, Kevin Grittner wrote:

matview-v4.patch.gz

Hi,

I was wondering if material views should not go into information_schema. I was thinking either
.views or .tables. Have you considered this?

I ask because as far as I can see querying for mv's has to go like this:

SELECT n.nspname, c.relname
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('m','')
and n.nspname = 'myschema'

which seems rather ugly.

Also, some documentation typos: please see attached.

Thanks,

Erik Rijkers

Attachments:

matviewtypos.diffapplication/octet-stream; name=matviewtypos.diffDownload
--- doc/src/sgml/ref/alter_materialized_view.sgml.orig	2013-02-19 23:00:52.091597166 +0100
+++ doc/src/sgml/ref/alter_materialized_view.sgml	2013-02-19 23:01:15.923597583 +0100
@@ -21,8 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
-    <replaceable class="PARAMETER">action</replaceable> [, ... ]
+ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">action</replaceable> [, ... ]
 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>
--- doc/src/sgml/ref/refresh_materialized_view.sgml.orig	2013-02-19 23:00:52.101677488 +0100
+++ doc/src/sgml/ref/refresh_materialized_view.sgml	2013-02-19 23:02:08.469596567 +0100
@@ -15,7 +15,7 @@
   <refpurpose>refresh a materialized view</refpurpose>
  </refnamediv>
 
- <indexterm zone="sql-dropmaterializedview">
+ <indexterm zone="sql-refreshmaterializedview">
   <primary>REFRESH MATERIALIZED VIEW</primary>
  </indexterm>
 
@@ -54,7 +54,7 @@
   <title>Notes</title>
 
   <para>
-   While the the default index for future
+   While the default index for future
    <xref linkend="SQL-CLUSTER">
    operations is retained, <command>REFRESH MATERIALIZED VIEW</> does not
    order the generated rows based on this property. If you want the data
#41Josh Berkus
josh@agliodbs.com
In reply to: Erik Rijkers (#40)

On 02/19/2013 02:09 PM, Erik Rijkers wrote:

On Sat, February 16, 2013 02:01, Kevin Grittner wrote:

matview-v4.patch.gz

Hi,

I was wondering if material views should not go into information_schema. I was thinking either
.views or .tables. Have you considered this?

I ask because as far as I can see querying for mv's has to go like this:

SELECT n.nspname, c.relname
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('m','')
and n.nspname = 'myschema'

Well, I'm not sure about information_schema, but we'll definitely want a
pg_matviews system view. Also a \dM. That could wait until 9.4, though.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42David Fetter
david@fetter.org
In reply to: Erik Rijkers (#40)

On Tue, Feb 19, 2013 at 11:09:13PM +0100, Erik Rijkers wrote:

On Sat, February 16, 2013 02:01, Kevin Grittner wrote:

matview-v4.patch.gz

Hi,

I was wondering if material views should not go into information_schema. I was thinking either
.views or .tables. Have you considered this?

I'm guessing it'd be .views if anything. Haven't been able to
decipher from section 11 of the standard (Schemata) whether the
standard has anything to say on the matter.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Kevin Grittner
kgrittn@ymail.com
In reply to: Erik Rijkers (#40)

Erik Rijkers <er@xs4all.nl> wrote:

I was wondering if material views should not go into
information_schema.  I was thinking either .views or .tables.
Have you considered this?

I had not considered this to be a good idea because
information_schema is defined by the standard, and materialized
views are an extension to the standard.  Someone using these views
to identify either tables or views might make a bad choice based on
this.  I'm open to arguments for inclusion, if you think it would
not violate the standard.  Which would be safe?

Also, some documentation typos: please see attached.

Will apply.  Thanks.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Kevin Grittner
kgrittn@ymail.com
In reply to: Josh Berkus (#41)

Josh Berkus <josh@agliodbs.com> wrote:

Well, I'm not sure about information_schema, but we'll definitely
want a pg_matviews system view.

That could wait until 9.4, though.

That I could probably do.  Do you think they should have a separate
pg_stat_user_matviews table, etc., or do you think it would be
better to include them in with tables there?

Also a \dM.

I already added it as \dm in the current patch.  Does that conflict
with something else that's pending?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Josh Berkus
josh@agliodbs.com
In reply to: Kevin Grittner (#44)

That I could probably do. Do you think they should have a separate
pg_stat_user_matviews table, etc., or do you think it would be
better to include them in with tables there?

Well, ideally pg_matviews would have matview definitions, and
pg_stat_matviews would have stats on matview usage and rows. But see
what you can get done; I imagine we'll overhaul it for 9.4 anyway once
we've had a chance to use the feature.

Also a \dM.

I already added it as \dm in the current patch. Does that conflict
with something else that's pending?

Oh, no, I thought \dm was *already* in use, but apparently not.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#24)

Kevin Grittner <kgrittn@ymail.com>

There was one minor syntax issue not addressed by Noah, nor much
discussed in general that I didn't want to just unilaterally
choose; but given that nobody seems to care that much I will put
forward a proposal and do it that way tomorrow if nobody objects.
Before this patch tables were the only things subject to
truncation, but now materialized views can also be truncated.  So
far we have been treating TABLE as a noise word in the truncate
command.  I assume we still want to allow tables to be truncated
with or without the word.  The question is what to do about
materialized views, and wheter both can be specified on a single
TRUNCATE statement.  I propose that we allow TABLE or MATERIALIZED
VIEW to be specified, or that part of the statement to be left out.
I propose that either type of object be allowed unless one or the
other is specified and the object to be truncated is not of that
kind.  So you could mix both kinds on one statement, so long as you
didn't specify either kind.

When I went to do this, I hit a shift/reduce conflict, because with
TABLE being optional it couldn't tell whether:

TRUNCATE MATERIALIZED VIEW x, y, z;

... was looking for five relations or three.  That goes away with
MATERIALIZED escalated to TYPE_FUNC_NAME_KEYWORD.  Is that OK?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Kevin Grittner
kgrittn@ymail.com
In reply to: Josh Berkus (#45)
1 attachment(s)

Josh Berkus <josh@agliodbs.com> wrote:

That I could probably do.  Do you think they should have a separate
pg_stat_user_matviews table, etc., or do you think it would be
better to include them in with tables there?

Well, ideally pg_matviews would have matview definitions, and
pg_stat_matviews would have stats on matview usage and rows.  But see
what you can get done; I imagine we'll overhaul it for 9.4 anyway once
we've had a chance to use the feature.

I agree on pg_matviews, but after looking over the existing views
and thinking about what I would use them for as a DBA, I'm inclined
to fold the backing tables for MVs into the _stat_ and _statio_
views -- especially since we already include the backing tables and
indexes for TOAST.  There is a precident for including
implementation details at that level.  The only difference from
TOAST, is that I include the heap and indexes for MVs in the _user_
views.  I'm attaching the patch for just the system_views.sql file
for discussion before I go write docs for this part.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

matview-system_views-v1.difftext/x-patch; name=matview-system_views-v1.diffDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c479c23..711a2ba 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -94,6 +94,18 @@ CREATE VIEW pg_tables AS
          LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
     WHERE C.relkind = 'r';
 
+CREATE VIEW pg_matviews AS
+    SELECT
+        N.nspname AS schemaname,
+        C.relname AS matviewname,
+        pg_get_userbyid(C.relowner) AS matviewowner,
+        T.spcname AS tablespace,
+        C.relhasindex AS hasindexes,
+        pg_get_viewdef(C.oid) AS definition
+    FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+         LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
+    WHERE C.relkind = 'm';
+
 CREATE VIEW pg_indexes AS
     SELECT
         N.nspname AS schemaname,
@@ -402,7 +414,7 @@ CREATE VIEW pg_stat_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_xact_all_tables AS
@@ -422,7 +434,7 @@ CREATE VIEW pg_stat_xact_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_sys_tables AS
@@ -467,7 +479,7 @@ CREATE VIEW pg_statio_all_tables AS
             pg_class T ON C.reltoastrelid = T.oid LEFT JOIN
             pg_class X ON T.reltoastidxid = X.oid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname, T.oid, X.oid;
 
 CREATE VIEW pg_statio_sys_tables AS
@@ -494,7 +506,7 @@ CREATE VIEW pg_stat_all_indexes AS
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't');
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_stat_sys_indexes AS
     SELECT * FROM pg_stat_all_indexes
@@ -520,7 +532,7 @@ CREATE VIEW pg_statio_all_indexes AS
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't');
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_statio_sys_indexes AS
     SELECT * FROM pg_statio_all_indexes
#48Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#47)
1 attachment(s)

Kevin Grittner <kgrittn@ymail.com> wrote:

I'm attaching the patch for just the system_views.sql file
for discussion before I go write docs for this part.

Meh.  If I'm gonna have pg_matviews I might as well include an
isscannable column.  v2 attached.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

matview-system_views-v2.difftext/x-patch; name=matview-system_views-v2.diffDownload
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c479c23..ccf0bd8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -94,6 +94,19 @@ CREATE VIEW pg_tables AS
          LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
     WHERE C.relkind = 'r';
 
+CREATE VIEW pg_matviews AS
+    SELECT
+        N.nspname AS schemaname,
+        C.relname AS matviewname,
+        pg_get_userbyid(C.relowner) AS matviewowner,
+        T.spcname AS tablespace,
+        C.relhasindex AS hasindexes,
+        pg_relation_is_scannable(C.oid) AS isscannable,
+        pg_get_viewdef(C.oid) AS definition
+    FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+         LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
+    WHERE C.relkind = 'm';
+
 CREATE VIEW pg_indexes AS
     SELECT
         N.nspname AS schemaname,
@@ -402,7 +415,7 @@ CREATE VIEW pg_stat_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_xact_all_tables AS
@@ -422,7 +435,7 @@ CREATE VIEW pg_stat_xact_all_tables AS
     FROM pg_class C LEFT JOIN
          pg_index I ON C.oid = I.indrelid
          LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname;
 
 CREATE VIEW pg_stat_sys_tables AS
@@ -467,7 +480,7 @@ CREATE VIEW pg_statio_all_tables AS
             pg_class T ON C.reltoastrelid = T.oid LEFT JOIN
             pg_class X ON T.reltoastidxid = X.oid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't')
+    WHERE C.relkind IN ('r', 't', 'm')
     GROUP BY C.oid, N.nspname, C.relname, T.oid, X.oid;
 
 CREATE VIEW pg_statio_sys_tables AS
@@ -494,7 +507,7 @@ CREATE VIEW pg_stat_all_indexes AS
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't');
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_stat_sys_indexes AS
     SELECT * FROM pg_stat_all_indexes
@@ -520,7 +533,7 @@ CREATE VIEW pg_statio_all_indexes AS
             pg_index X ON C.oid = X.indrelid JOIN
             pg_class I ON I.oid = X.indexrelid
             LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
-    WHERE C.relkind IN ('r', 't');
+    WHERE C.relkind IN ('r', 't', 'm');
 
 CREATE VIEW pg_statio_sys_indexes AS
     SELECT * FROM pg_statio_all_indexes
#49Josh Berkus
josh@agliodbs.com
In reply to: Kevin Grittner (#48)

On 02/19/2013 03:41 PM, Kevin Grittner wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

I'm attaching the patch for just the system_views.sql file
for discussion before I go write docs for this part.

Meh. If I'm gonna have pg_matviews I might as well include an
isscannable column. v2 attached.

pg_get_viewdef() will work on Matviews? Great.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#46)

Kevin Grittner <kgrittn@ymail.com> writes:

When I went to do this, I hit a shift/reduce conflict, because with
TABLE being optional it couldn't tell whether:

TRUNCATE MATERIALIZED VIEW x, y, z;

... was looking for five relations or three.� That goes away with
MATERIALIZED escalated to TYPE_FUNC_NAME_KEYWORD.� Is that OK?

Not really. I would much rather see us not bother with this pedantic
syntax than introduce an even-partially-reserved word.

Having said that, I don't think I believe your analysis of why this
doesn't work. The presence or absence of commas ought to make the
syntax non-ambiguous, I would think. Maybe you just factored the
grammar wrong.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#39)

On Tue, Feb 19, 2013 at 11:01 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/19/13 8:54 AM, Robert Haas wrote:

In the department of crazy ideas, what about having pg_dump NEVER
refresh ANY materialized views?

It might be useful to have an option for this, but I don't think it
should be the default. The default should be that the new database is
"ready to go".

Then again, when would you ever actually use that option?

You'd use that option if you'd rather get the database mostly-up as
soon as possible, and then worry about the materialized views
afterwards.

This might be different if there were a command to refresh all
materialized views, because you don't want to have to go around and type
separate commands 47 times after a restore.

Well, it's pretty easy to do:

SELECT 'LOAD MATERIALIZED VIEW ' || p.oid::regclass || ';' FROM
pg_class WHERE relkind = 'm';

...but we could also add explicit syntax for it, perhaps along the
lines of what we have for CLUSTER and VACUUM.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#51)

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Feb 19, 2013 at 11:01 AM, Peter Eisentraut <peter_e@gmx.net> wrote:

This might be different if there were a command to refresh all
materialized views, because you don't want to have to go around and type
separate commands 47 times after a restore.

Well, it's pretty easy to do:

SELECT 'LOAD MATERIALIZED VIEW ' || p.oid::regclass || ';' FROM
pg_class WHERE relkind = 'm';

...but we could also add explicit syntax for it, perhaps along the
lines of what we have for CLUSTER and VACUUM.

It's not really that easy, because of the likelihood that MVs have to be
refreshed in a specific order. The SELECT you suggest definitely seems
too simplistic. A dedicated command could perhaps be built to pay
attention to dependencies ... but if we're still coding such things now,
it seems a bit late for 9.3.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Peter Eisentraut
peter_e@gmx.net
In reply to: David Fetter (#42)

On 2/19/13 5:22 PM, David Fetter wrote:

On Tue, Feb 19, 2013 at 11:09:13PM +0100, Erik Rijkers wrote:

On Sat, February 16, 2013 02:01, Kevin Grittner wrote:

matview-v4.patch.gz

Hi,

I was wondering if material views should not go into information_schema. I was thinking either
.views or .tables. Have you considered this?

I'm guessing it'd be .views if anything. Haven't been able to
decipher from section 11 of the standard (Schemata) whether the
standard has anything to say on the matter.

I suppose one should be able to expect that if one finds a view in the
information schema, then one should be able to use DROP VIEW to remove
it. Which in this case wouldn't work. So I don't think including a
materialized view under views or tables is appropriate.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#54Kevin Grittner
kgrittn@ymail.com
In reply to: Tom Lane (#50)

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

When I went to do this, I hit a shift/reduce conflict, because
with TABLE being optional it couldn't tell whether:

TRUNCATE MATERIALIZED VIEW x, y, z;

... was looking for five relations or three.  That goes away
with MATERIALIZED escalated to TYPE_FUNC_NAME_KEYWORD.  Is that
OK?

Not really.  I would much rather see us not bother with this
pedantic syntax than introduce an even-partially-reserved word.

I'm not sure it's worth it either; but two people requested it and
I didn't forsee this shift/reduce conflict, so I took a shot at it.
 If we can't eliminate the conflict, I'm fine with leaving things
as they are in the latest posted patch.

Having said that, I don't think I believe your analysis of why
this doesn't work.  The presence or absence of commas ought to
make the syntax non-ambiguous, I would think.  Maybe you just
factored the grammar wrong.

Well, it wouldn't be the first time you've seen a better way to do
something in flex than I was able to see.  Taking just the gram.y
part of the change which implemented this, and omitting the change
in reservedness of MATERIALIZED, I have:

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 820cb41..1d393c5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -394,6 +394,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 %type <ival>   opt_column event cursor_options opt_hold opt_set_data
 %type <objtype>    reindex_type drop_type comment_type security_label_type
+               trunc_type

 %type <node>   fetch_args limit_clause select_limit_value
                offset_clause select_offset_value
@@ -5172,9 +5173,10 @@ attrs:       '.' attr_name
  *****************************************************************************/

 TruncateStmt:
-           TRUNCATE opt_table relation_expr_list opt_restart_seqs opt_drop_behavior
+           TRUNCATE trunc_type relation_expr_list opt_restart_seqs opt_drop_behavior
                {
                    TruncateStmt *n = makeNode(TruncateStmt);
+                   n->objtype = $2;
                    n->relations = $3;
                    n->restart_seqs = $4;
                    n->behavior = $5;
@@ -5182,6 +5184,12 @@ TruncateStmt:
                }
        ;
+trunc_type:
+           TABLE                       { $$ = OBJECT_TABLE; }
+           | MATERIALIZED VIEW         { $$ = OBJECT_MATVIEW; }
+           | /*EMPTY*/                 { $$ = OBJECT_UNSPECIFIED; }
+       ;
+
 opt_restart_seqs:
            CONTINUE_P IDENTITY_P       { $$ = false; }
            | RESTART IDENTITY_P        { $$ = true; }

I'm open to suggestions on a better way.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Kevin Grittner
kgrittn@ymail.com
In reply to: Peter Eisentraut (#53)

Peter Eisentraut <peter_e@gmx.net> wrote:

I suppose one should be able to expect that if one finds a view
in the information schema, then one should be able to use DROP
VIEW to remove it.  Which in this case wouldn't work.  So I don't
think including a materialized view under views or tables is
appropriate.

Right.  I think adding pg_matviews covers the stated use-case
enough to answer Erik's concern.  I'm not going to mess with adding
non-standard stuff to the standard views.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#54)

Kevin Grittner <kgrittn@ymail.com> writes:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Having said that, I don't think I believe your analysis of why
this doesn't work.

Well, it wouldn't be the first time you've seen a better way to do
something in flex than I was able to see. Taking just the gram.y
part of the change which implemented this, and omitting the change
in reservedness of MATERIALIZED, I have:

-           TRUNCATE opt_table relation_expr_list opt_restart_seqs opt_drop_behavior
+           TRUNCATE trunc_type relation_expr_list opt_restart_seqs opt_drop_behavior
+trunc_type:
+           TABLE                       { $$ = OBJECT_TABLE; }
+           | MATERIALIZED VIEW         { $$ = OBJECT_MATVIEW; }
+           | /*EMPTY*/                 { $$ = OBJECT_UNSPECIFIED; }
+       ;

Yeah, this is a standard gotcha when working with unreserved keywords.
You can't factor it like that because then the parser is required to
make a shift-reduce decision (on whether to reduce trunc_type to empty)
before it can "see past" the first word. So for instance given

TRUNCATE MATERIALIZED ...
^

the parser has to make that decision when it can't see past the word
"MATERIALIZED" and so doesn't know what comes after it.

The way to fix it is to not try to use the sub-production but spell it
all out:

TRUNCATE TABLE relation_expr_list ...
| TRUNCATE MATERIALIZED VIEW relation_expr_list ...
| TRUNCATE relation_expr_list ...

Now the parser doesn't have to make any shift-reduce decision until
after it can "see past" the first identifier. It's a bit tedious
but beats making a word more reserved than it has to be.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Kevin Grittner
kgrittn@ymail.com
In reply to: Tom Lane (#56)

Tom Lane <tgl@sss.pgh.pa.us> wrote:

The way to fix it is to not try to use the sub-production but spell it
all out:

      TRUNCATE TABLE relation_expr_list ...
    | TRUNCATE MATERIALIZED VIEW relation_expr_list ...
    | TRUNCATE relation_expr_list ...

Now the parser doesn't have to make any shift-reduce decision until
after it can "see past" the first identifier.  It's a bit tedious
but beats making a word more reserved than it has to be.

Thanks!  Will do.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#51)

On 2/20/13 6:13 AM, Robert Haas wrote:

It might be useful to have an option for this, but I don't think it

should be the default. The default should be that the new database is
"ready to go".

Then again, when would you ever actually use that option?

You'd use that option if you'd rather get the database mostly-up as
soon as possible, and then worry about the materialized views
afterwards.

Since the proposed materialized views are not available for implicit use
in query optimization, the only way an application would make use of
them is to access them directly. And if it accesses an unpopulated
materialized view, it would fail. So I don't think in the current state
a database is mostly-up without the materialized views filled in.

I can see the value in having a restore mode that postpones certain
nonessential operations, such as creating indexes or certain constraints
or even materialized views. But I think the boundaries and expectations
for that need to be defined more precisely. For example, a database
without constraints might be considered "ready for read-only use",
without secondary indexes it might be "ready for use but slow".

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Erik Rijkers
er@xs4all.nl
In reply to: Kevin Grittner (#55)

On Wed, February 20, 2013 16:28, Kevin Grittner wrote:

Peter Eisentraut <peter_e@gmx.net> wrote:

I suppose one should be able to expect that if one finds a view
in the information schema, then one should be able to use DROP
VIEW to remove it.  Which in this case wouldn't work.  So I don't
think including a materialized view under views or tables is
appropriate.

Right.  I think adding pg_matviews covers the stated use-case
enough to answer Erik's concern. 

Absolutely - I agree pg_matviews is much better than adding deviating information_schema stuff.

Thank you,

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Greg Stark
stark@mit.edu
In reply to: Kevin Grittner (#57)

On Wed, Feb 20, 2013 at 4:26 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

The way to fix it is to not try to use the sub-production but spell it
all out:

TRUNCATE TABLE relation_expr_list ...
| TRUNCATE MATERIALIZED VIEW relation_expr_list ...
| TRUNCATE relation_expr_list ...

Now the parser doesn't have to make any shift-reduce decision until
after it can "see past" the first identifier. It's a bit tedious
but beats making a word more reserved than it has to be.

Thanks! Will do.

Fwiw I think worrying about stuff like this at this point is probably
a waste of time. There'll be a period of bike-shedding where people
debate what the command should be called so worrying about parser
conflicts before there's a consensus is kind pointless.

I would like to know what operations you plan to support independently
of the command names. I may have missed much earlier in the discussion
but then I suspect things have evolved since then.

It sounds like you want to support:

1) Selecting from materialized viws
2) Manually refreshing materialized views
3) Manually truncating materialized views

And explicitly not support

1) Automatically rewriting queries to select from matching views
2) Incrementally refreshing materialized views
3) Manual DML against data in materialized views (except truncate
which is kind of DDL)
4) Keeping track of whether the data in the materialized view is up to date

I have to say I find this model a bit odd. It seems the UI you're
presenting is that they're basically read-only tables that the
database will fill in the data for automatically. My mental model of
materialized views is that they're basically views that the database
guarantees a different performance characteristic for.

I would expect a materialized view to be up to date all the time. If
we don't support incremental updates (which seems like a fine thing
not to support in a first cut) then I would expect any DML against the
table to mark the view invalid and any queries against it to produce
an error (or possibly go to the source tables using the view
definition but that's probably a bad idea for most use cases). Ie.
they should behave like a view at all times and have up to date
information or fail entirely.

I would expect a command like TRUNCATE MATERIALIZED VIEW to exist but
I would expect it to be called something like INVALIDATE rather than
TRUNCATE and dropping the storage is a side effect of simply telling
the database that it doesn't need to maintain this materialized view.
Though I could be convinced "truncate" is a good name as long as it's
documented well.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Kevin Grittner
kgrittn@ymail.com
In reply to: Greg Stark (#60)

Greg Stark <stark@mit.edu> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

The way to fix it is to not try to use the sub-production but
spell it all out:

       TRUNCATE TABLE relation_expr_list ...
     | TRUNCATE MATERIALIZED VIEW relation_expr_list ...
     | TRUNCATE relation_expr_list ...

Now the parser doesn't have to make any shift-reduce decision
until after it can "see past" the first identifier.  It's a bit
tedious but beats making a word more reserved than it has to
be.

Thanks!  Will do.

Fwiw I think worrying about stuff like this at this point is
probably a waste of time. There'll be a period of bike-shedding
where people debate what the command should be called so worrying
about parser conflicts before there's a consensus is kind
pointless.

That sort of bikeshedding already happened three months ago.  Too
late now.

I would like to know what operations you plan to support
independently of the command names. I may have missed much
earlier in the discussion but then I suspect things have evolved
since then.

It sounds like you want to support:

1) Selecting from materialized viws
2) Manually refreshing materialized views
3) Manually truncating materialized views

And explicitly not support

1) Automatically rewriting queries to select from matching views
2) Incrementally refreshing materialized views

Those are material for later releases, building on the base of what
goes into this release.

3) Manual DML against data in materialized views (except truncate
which is kind of DDL)

There is quite a lot of DML allowed -- changing tablespace,
changing schema, changing name of the MV or of individual columns
in it, changing statistics targets, creating indexes, and other
operations are supported.

4) Keeping track of whether the data in the materialized view is
up to date

Only keeping track of whether data has been populated or not, for
now.  There has been agreement that one or more timestamps relating
to freshness will make sense, but these are not in the initial
patch.

I have to say I find this model a bit odd.

It's not a model, it's a starting point.  Several people have
already said that even this much is useful and they expect to take
advantage of it.  I'm doing what I can to not paint us into a
corner where it's hard to extend to all the features everyone
dreams of, but if we waited for that to commit something, it will
never happen.

I would expect a materialized view to be up to date all the time.

I expect that this will eventually be an option, but I expect that
is will be a seldom-used one.  Most cases that I've seen, people
want summary data that is reasonably up-to-date without unduly
affecting the performance of incremental changes to the underlying
data.  I've sketched out the roadmap from this patch to all of
these options in a vauge, handwavy fashion, and don't have a lot of
interest in taking it farther until we're past 9.3 beta.

If we don't support incremental updates (which seems like a fine
thing not to support in a first cut) then I would expect any DML
against the table to mark the view invalid and any queries
against it to produce an error (or possibly go to the source
tables using the view definition but that's probably a bad idea
for most use cases). Ie. they should behave like a view at all
times and have up to date information or fail entirely.

That would render them completely useless for the use-cases I've
seen.  If you want to offer a patch to do that as an option, feel
free, but I will strongly argue against that as unconditional
behavior.

I would expect a command like TRUNCATE MATERIALIZED VIEW to exist
but I would expect it to be called something like INVALIDATE
rather than TRUNCATE and dropping the storage is a side effect of
simply telling the database that it doesn't need to maintain this
materialized view. Though I could be convinced "truncate" is a
good name as long as it's documented well.

I'm trying to minimize the number of new keywords.  The initial
patch only added MATERIALIZED.  I added REFRESH due to
near-universal demand for something other than the LOAD
MATERIALIZED VIEW I initially used.  Have you seen the statistics
Tom gave out on how much the size of the executable bloats with
every new keyword?  Until now nobody has expressed concern about
TRUNCATE MATERIALIZED VIEW, so it would take quite a groundswell of
concern at this point to even consider a new keyword for this
functionality this late in the game.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Josh Berkus
josh@agliodbs.com
In reply to: Greg Stark (#60)

And explicitly not support

1) Automatically rewriting queries to select from matching views
2) Incrementally refreshing materialized views
3) Manual DML against data in materialized views (except truncate
which is kind of DDL)
4) Keeping track of whether the data in the materialized view is up to date

The idea is to add the above features over the next few versions of
Postgres.

I have to say I find this model a bit odd. It seems the UI you're
presenting is that they're basically read-only tables that the
database will fill in the data for automatically.

This is what matviews are in other DBMSes.

My mental model of
materialized views is that they're basically views that the database
guarantees a different performance characteristic for.

How would we do that, exactly? That would be lovely, but unless you
have a way to accomplish it ...

I would expect a materialized view to be up to date all the time.

Actually, there's a huge use case for asynchronously updated matviews,
so we would not want an implementation which ruled them out. Also
there's the argument that synchronously updated matviews have little
actual performance advantage over regular dynamic views.

Or to put it another way: I could use this feature, as it is, in about 8
different projects I'm currently supporting. I personally can't think
of a single project where I need synchronously updated matviews,
currently. I have in the past, but it's a LOT less frequent that the
desire for async, just as the desire for async replication is more
common than the desire for syncrep.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Peter Eisentraut
peter_e@gmx.net
In reply to: Kevin Grittner (#46)

On 2/19/13 5:47 PM, Kevin Grittner wrote:

When I went to do this, I hit a shift/reduce conflict, because with
TABLE being optional it couldn't tell whether:

TRUNCATE MATERIALIZED VIEW x, y, z;

... was looking for five relations or three. That goes away with
MATERIALIZED escalated to TYPE_FUNC_NAME_KEYWORD. Is that OK?

Is TRUNCATE even the right command here? For regular tables TRUNCATE is
a fast DELETE, which logically empties the table. For materialized
views, there is no deleting, so this command (I suppose?) just
invalidates the materalized view. That's not the same thing.

Are there TRUNCATE triggers on materialized views?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Kevin Grittner
kgrittn@ymail.com
In reply to: Peter Eisentraut (#63)

Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/19/13 5:47 PM, Kevin Grittner wrote:

When I went to do this, I hit a shift/reduce conflict, because
with TABLE being optional it couldn't tell whether:

TRUNCATE MATERIALIZED VIEW x, y, z;

... was looking for five relations or three.  That goes away
with MATERIALIZED escalated to TYPE_FUNC_NAME_KEYWORD.  Is that
OK?

Is TRUNCATE even the right command here?  For regular tables
TRUNCATE is a fast DELETE, which logically empties the table.
For materialized views, there is no deleting, so this command (I
suppose?) just invalidates the materalized view.  That's not the
same thing.

Hmm.  That's what Greg Stark just said, and I replied that nobody
else had raised the issue in over three months.  With Greg, that's
two now.

TRUNCATE MATERIALIZED VIEW discards any data which has been loaded
into the MV, rendering it unavailable for scanning.  Internally, it
does do a truncate, exactly as truncate table.  The resulting
zero-length heap file is what is used to determine whether a
materialized view is "scannable".  When a CREATE WITH DATA or a
REFRESH generates zero rows, an empty single page is created to
indicate that it is scannable (valid to use in queries) but
contains no rows.

I agree that INVALIDATE is probably more descriptive, although it
seems that there might be some even better word if we bikeshed
enough.  The question is, is it worth creating a new keyword to
call the internal truncate function for materialized views, versus
documenting that truncating a materialized view renders it invalid?
Again, given the numbers that Tom presented a while back about the
space requirements of every new keyword, I don't think this is
enough of a gain to justify that.  I still squirm a little about
having used REFRESH, even though demand for that was overwhelming.

Are there TRUNCATE triggers on materialized views?

No.  Nor SELECT, INSERT, UPDATE, or DELETE triggers.  You can't
create a trigger of any type on a materialized view.  I don't think
that would interfere with event triggers, though.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Stephen Frost
sfrost@snowman.net
In reply to: Kevin Grittner (#64)

* Kevin Grittner (kgrittn@ymail.com) wrote:

Peter Eisentraut <peter_e@gmx.net> wrote:

Is TRUNCATE even the right command here?  For regular tables
TRUNCATE is a fast DELETE, which logically empties the table.
For materialized views, there is no deleting, so this command (I
suppose?) just invalidates the materalized view.  That's not the
same thing.

Hmm.  That's what Greg Stark just said, and I replied that nobody
else had raised the issue in over three months.  With Greg, that's
two now.

TRUNCATE MAT VIEW seems like the right command to me. Just my 2c.

Thanks,

Stephen

#66Peter Eisentraut
peter_e@gmx.net
In reply to: Kevin Grittner (#64)

On 2/20/13 2:30 PM, Kevin Grittner wrote:

Are there TRUNCATE triggers on materialized views?

No. Nor SELECT, INSERT, UPDATE, or DELETE triggers. You can't
create a trigger of any type on a materialized view. I don't think
that would interfere with event triggers, though.

More generally, I would consider the invalidation of a materialized view
a DDL command, whereas truncating a table is a DML command. This has
various implications with triggers, logging, permissions. I think it's
not good to mix those two.

Also note that un-invalidating==refreshing a materialized view is
already a DDL command.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Kevin Grittner
kgrittn@ymail.com
In reply to: Peter Eisentraut (#66)

Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/20/13 2:30 PM, Kevin Grittner wrote:

Are there TRUNCATE triggers on materialized views?

No.  Nor SELECT, INSERT, UPDATE, or DELETE triggers.  You can't
create a trigger of any type on a materialized view.  I don't
think that would interfere with event triggers, though.

More generally, I would consider the invalidation of a
materialized view a DDL command, whereas truncating a table is a
DML command.

The force of that assertion is somewhat undercut by the fact that
the ExecuteTruncate() function does exactly what needs to be done
to discard the data in a materialized view and make it appear as
non-scannable.  Even if we dress it up with different syntax, it's
not clear that we wouldn't build a TruncateStmt in the parser and
pass it through exactly the same execution path.  We would just
need to look at the relkind to generate a different command tag.

This has various implications with triggers, logging,
permissions.  I think it's not good to mix those two.

Could you give a more concrete example of where you see a problem?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Tatsuo Ishii
ishii@postgresql.org
In reply to: Greg Stark (#60)

I would like to know what operations you plan to support independently
of the command names. I may have missed much earlier in the discussion
but then I suspect things have evolved since then.

It sounds like you want to support:

1) Selecting from materialized viws
2) Manually refreshing materialized views
3) Manually truncating materialized views

Maybe plus?

4) Automatically dropping materialized views if underlying table(s)
are dropped/altered

Or this has to be done manually?

And explicitly not support

1) Automatically rewriting queries to select from matching views
2) Incrementally refreshing materialized views
3) Manual DML against data in materialized views (except truncate
which is kind of DDL)
4) Keeping track of whether the data in the materialized view is up to date

I have to say I find this model a bit odd. It seems the UI you're
presenting is that they're basically read-only tables that the
database will fill in the data for automatically. My mental model of
materialized views is that they're basically views that the database
guarantees a different performance characteristic for.

I would expect a materialized view to be up to date all the time. If
we don't support incremental updates (which seems like a fine thing
not to support in a first cut) then I would expect any DML against the
table to mark the view invalid and any queries against it to produce
an error (or possibly go to the source tables using the view
definition but that's probably a bad idea for most use cases). Ie.
they should behave like a view at all times and have up to date
information or fail entirely.

I would expect a command like TRUNCATE MATERIALIZED VIEW to exist but
I would expect it to be called something like INVALIDATE rather than
TRUNCATE and dropping the storage is a side effect of simply telling
the database that it doesn't need to maintain this materialized view.
Though I could be convinced "truncate" is a good name as long as it's
documented well.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Greg Stark
stark@mit.edu
In reply to: Peter Eisentraut (#66)

On Wed, Feb 20, 2013 at 9:25 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

More generally, I would consider the invalidation of a materialized view
a DDL command, whereas truncating a table is a DML command.

That's not entirely true. From the database's point of view, TRUNCATE
is in many ways actually DDL.

I actually don't really dislike using "TRUNCATE" for this command. I
was more asking about whether this meant people were thinking of the
view as a thing where you could control the data in it by hand and
could have the view be "empty" rather than just "not valid".

The way I was thinking about it, whatever the command is named, you
might be able to tell the database to drop the storage associated with
the view but that would make the view invalid until it was refreshed.
It wouldn't make it appear to be empty.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#69)

Greg Stark <stark@mit.edu> writes:

The way I was thinking about it, whatever the command is named, you
might be able to tell the database to drop the storage associated with
the view but that would make the view invalid until it was refreshed.
It wouldn't make it appear to be empty.

Actually, that seems like a pretty key point to me. TRUNCATE TABLE
results in a table that is perfectly valid, you just deleted all the
rows that used to be in it. Throwing away an MV's contents should
not result in an MV that is considered valid. That being the case,
lumping them as being the "same" operation feels like the wrong thing,
and so we should choose a different name for the MV operation.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Andres Freund
andres@2ndquadrant.com
In reply to: Greg Stark (#69)

On 2013-02-21 04:14:09 +0000, Greg Stark wrote:

On Wed, Feb 20, 2013 at 9:25 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

More generally, I would consider the invalidation of a materialized view
a DDL command, whereas truncating a table is a DML command.

That's not entirely true. From the database's point of view, TRUNCATE
is in many ways actually DDL.

I actually don't really dislike using "TRUNCATE" for this command. I
was more asking about whether this meant people were thinking of the
view as a thing where you could control the data in it by hand and
could have the view be "empty" rather than just "not valid".

It also might get confusing when we get materialized views that are
auto-updateable. I am not suggesting to forward TRUNCATE to the internal
storage in that case but giving an error so its an easy to find
distinction to a normal table seems like a good idea.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#69)

On Wed, Feb 20, 2013 at 11:14 PM, Greg Stark <stark@mit.edu> wrote:

On Wed, Feb 20, 2013 at 9:25 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

More generally, I would consider the invalidation of a materialized view
a DDL command, whereas truncating a table is a DML command.

That's not entirely true. From the database's point of view, TRUNCATE
is in many ways actually DDL.

I actually don't really dislike using "TRUNCATE" for this command.

Me neither. I'm astonished that we're seriously considering adding
new keywords for this.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Peter Eisentraut
peter_e@gmx.net
In reply to: Kevin Grittner (#67)

On 2/20/13 5:03 PM, Kevin Grittner wrote:

Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/20/13 2:30 PM, Kevin Grittner wrote:

Are there TRUNCATE triggers on materialized views?

No. Nor SELECT, INSERT, UPDATE, or DELETE triggers. You can't
create a trigger of any type on a materialized view. I don't
think that would interfere with event triggers, though.

More generally, I would consider the invalidation of a
materialized view a DDL command, whereas truncating a table is a
DML command.

The force of that assertion is somewhat undercut by the fact that
the ExecuteTruncate() function does exactly what needs to be done
to discard the data in a materialized view and make it appear as
non-scannable. Even if we dress it up with different syntax, it's
not clear that we wouldn't build a TruncateStmt in the parser and
pass it through exactly the same execution path. We would just
need to look at the relkind to generate a different command tag.

This is a fall-out of the implementation, and that's fine (although I'd
personally still be in favor of putting that state in the catalog, not
into the block count on disk, effectively), but I'm talking about the
external interfaces we present.

This has various implications with triggers, logging,
permissions. I think it's not good to mix those two.

Could you give a more concrete example of where you see a problem?

* Logging: You can set things to log DDL commands only. I would want a
MV invalidation to be logged.

* Permissions: There is a TRUNCATE permission, would that apply here?
There is no refresh permission.

* Triggers: There are TRUNCATE triggers, but they don't apply here.

* Triggers: I don't know how event triggers work, but I'd like
materialized view events to be grouped together somehow.

* Don't know the opinion of sepgsql on all this.

I think what this all comes down to, as I've mentioned before, is that
the opposite of this proposed truncate operation is the refresh
operation, and that is a DDL command under ALTER MATERIALIZED VIEW.
Both of these fundamental operations -- truncate/refresh,
invalidate/validate, empty/refill, whatever -- should be grouped
together somehow, as far as syntax, as well logging, permissions,
trigger handling, and so on are concerned. You don't need a new command
or key word for this. How about ALTER MATERIALIZED VIEW DISCARD?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Peter Eisentraut
peter_e@gmx.net
In reply to: Greg Stark (#69)

On 2/20/13 11:14 PM, Greg Stark wrote:

That's not entirely true. From the database's point of view, TRUNCATE
is in many ways actually DDL.

Whether something is DDL or DML or a read operation (query) is not an
implementation detail, it's a user-exposed category. Since TRUNCATE is
logically equivalent to DELETE, it's a DML operation, as far as the user
is concerned.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Kevin Grittner
kgrittn@ymail.com
In reply to: Peter Eisentraut (#74)

Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/20/13 11:14 PM, Greg Stark wrote:

That's not entirely true. From the database's point of view,
TRUNCATE is in many ways actually DDL.

Whether something is DDL or DML or a read operation (query) is
not an implementation detail, it's a user-exposed category.
Since TRUNCATE is logically equivalent to DELETE, it's a DML
operation, as far as the user is concerned.

Not really.  It doesn't follow the same MVCC behavior as DML.  This
is user-visible, documented behavior.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Kevin Grittner
kgrittn@ymail.com
In reply to: Tom Lane (#70)

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

The way I was thinking about it, whatever the command is named, you
might be able to tell the database to drop the storage associated with
the view but that would make the view invalid until it was refreshed.
It wouldn't make it appear to be empty.

Actually, that seems like a pretty key point to me.  TRUNCATE TABLE
results in a table that is perfectly valid, you just deleted all the
rows that used to be in it.  Throwing away an MV's contents should
not result in an MV that is considered valid.

It doesn't.  That was one of the more contentious points in the
earlier bikeshedding phases.  Some felt that throwing away the
contents was a form of making the MV "out of date" and as such
didn't require any special handling.  Others, including myself,
felt that "data not present" was a distinct state from "generated
zero rows" and that attempting to scan a materialized view for
which data had not been generated must result in an error.  The
latter property has been maintained from the beginning -- or at
least that has been the intent.

test=# CREATE TABLE t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL);
CREATE TABLE
test=# CREATE MATERIALIZED VIEW tm AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA;
SELECT 0
test=# SELECT pg_relation_is_scannable('tm'::regclass);
 pg_relation_is_scannable
--------------------------
 f
(1 row)

test=# SELECT * FROM tm;
ERROR:  materialized view "tm" has not been populated
HINT:  Use the REFRESH MATERIALIZED VIEW command.
test=# REFRESH MATERIALIZED VIEW tm;
REFRESH MATERIALIZED VIEW
test=# SELECT pg_relation_is_scannable('tm'::regclass);
 pg_relation_is_scannable
--------------------------
 t
(1 row)

test=# TRUNCATE MATERIALIZED VIEW tm;
TRUNCATE TABLE
test=# SELECT * FROM tm;
ERROR:  materialized view "tm" has not been populated
HINT:  Use the REFRESH MATERIALIZED VIEW command.
test=# SELECT pg_relation_is_scannable('tm'::regclass);
 pg_relation_is_scannable
--------------------------
 f
(1 row)

That being the case, lumping them as being the "same" operation
feels like the wrong thing, and so we should choose a different
name for the MV operation.

There is currently no truncation of MV data without rendering the
MV unscannable.  Do you still feel it needs a different command
name?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Kevin Grittner
kgrittn@ymail.com
In reply to: Greg Stark (#69)

Greg Stark <stark@mit.edu> wrote:

I actually don't really dislike using "TRUNCATE" for this
command.  I was more asking about whether this meant people were
thinking of the view as a thing where you could control the data
in it by hand and could have the view be "empty" rather than just
"not valid".

You can either populate the MV in the CREATE command or by REFRESH,
and it will be scannable.  If it is created WITH NO DATA or
TRUNCATEd it is not scannable, generating an error on an attempt to
reference it.

test=# select * from tm;
 type | totamt
------+--------
 y    |     12
 z    |     24
 x    |      5
(3 rows)

test=# truncate tm;
TRUNCATE TABLE
test=# select * from tm;
ERROR:  materialized view "tm" has not been populated
HINT:  Use the REFRESH MATERIALIZED VIEW command.

The way I was thinking about it, whatever the command is named,
you might be able to tell the database to drop the storage
associated with the view but that would make the view invalid
until it was refreshed.  It wouldn't make it appear to be empty.

I think we're on the same page after all.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Kevin Grittner (#76)

On 21.02.2013 16:38, Kevin Grittner wrote:

Tom Lane<tgl@sss.pgh.pa.us> wrote:

Greg Stark<stark@mit.edu> writes:

The way I was thinking about it, whatever the command is named, you
might be able to tell the database to drop the storage associated with
the view but that would make the view invalid until it was refreshed.
It wouldn't make it appear to be empty.

Actually, that seems like a pretty key point to me. TRUNCATE TABLE
results in a table that is perfectly valid, you just deleted all the
rows that used to be in it. Throwing away an MV's contents should
not result in an MV that is considered valid.

It doesn't. That was one of the more contentious points in the
earlier bikeshedding phases. Some felt that throwing away the
contents was a form of making the MV "out of date" and as such
didn't require any special handling. Others, including myself,
felt that "data not present" was a distinct state from "generated
zero rows" and that attempting to scan a materialized view for
which data had not been generated must result in an error. The
latter property has been maintained from the beginning -- or at
least that has been the intent.

Yeah, "data not present" is clearly different from "0 rows". I'm
surprised there isn't an explicit boolean column somewhere for that, but
I guess you can use the size of the heap for that too, as long as you're
careful to not truncate it to 0 blocks when it's empty but scannable.

There's at least one bug left in that area:

postgres=# create table t (id int4);
CREATE TABLE
postgres=# create materialized view tm as select * from t where id <
0;SELECT 0
postgres=# select * from tm;
id
----
(0 rows)

postgres=# create index i_tm on tm(id);CREATE INDEX
postgres=# cluster tm using i_tm;
CLUSTER
postgres=# select * from tm;
ERROR: materialized view "tm" has not been populated
HINT: Use the REFRESH MATERIALIZED VIEW command.

Clustering a materialized view invalidates it.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Kevin Grittner
kgrittn@ymail.com
In reply to: Peter Eisentraut (#73)

Peter Eisentraut <peter_e@gmx.net> wrote:

I'll need more time to ponder your other points, but...

You don't need a new command or key word for this.  How about
ALTER MATERIALIZED VIEW DISCARD?

I don't like this because we don't have ALTER TABLE REINDEX.  But
the fact that DISCARD is a keyword does open up some interesting
syntax possibilities without adding more keywords.  Maybe:

DISCARD MATERIALIZED VIEW DATA tm;

Or something like that.  Paint buckets are over there by the
bikeshed.  Have at it.  :-)
 
--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Kevin Grittner
kgrittn@ymail.com
In reply to: Andres Freund (#71)

Andres Freund <andres@2ndquadrant.com> wrote:

giving an error so its an easy to find distinction to a normal
table seems like a good idea.

I'm not sure I understood your concerns entirely, but wonder
whether this helps?:

test=# \d
              List of relations
 Schema | Name  |       Type        |  Owner
--------+-------+-------------------+---------
 public | bb    | materialized view | kgrittn
 public | t     | table             | kgrittn
 public | tm    | materialized view | kgrittn
 public | tmm   | materialized view | kgrittn
 public | tv    | view              | kgrittn
 public | tvmm  | materialized view | kgrittn
 public | tvv   | view              | kgrittn
 public | tvvm  | materialized view | kgrittn
 public | tvvmv | view              | kgrittn
(9 rows)

test=# truncate table tm;
ERROR:  "tm" is not a table
test=# truncate materialized view t;
ERROR:  "t" is not a materialized view
test=# truncate materialized view tm;
TRUNCATE TABLE
test=# truncate table t;
TRUNCATE TABLE

Well, maybe those command tags could use a tweak.

Then there's this, if you don't specify an object type:

test=# truncate t, tm;
TRUNCATE TABLE

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#76)

Kevin Grittner <kgrittn@ymail.com> writes:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

That being the case, lumping them as being the "same" operation
feels like the wrong thing, and so we should choose a different
name for the MV operation.

There is currently no truncation of MV data without rendering the
MV unscannable.� Do you still feel it needs a different command
name?

You didn't say anything that changed my opinion: it doesn't feel like
a TRUNCATE to me. It's not changing the object to a different but
entirely valid state, which is what TRUNCATE does.

Peter claimed upthread that REFRESH is a subcommand of ALTER MATERIALIZE
VIEW and that this operation should be another one. That sounds pretty
reasonable from here.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Kevin Grittner
kgrittn@ymail.com
In reply to: Heikki Linnakangas (#78)

Heikki Linnakangas <hlinnakangas@vmware.com> wrote:

On 21.02.2013 16:38, Kevin Grittner wrote:

Tom Lane<tgl@sss.pgh.pa.us>  wrote:

Greg Stark<stark@mit.edu>  writes:

The way I was thinking about it, whatever the command is named, you
might be able to tell the database to drop the storage associated with
the view but that would make the view invalid until it was refreshed.
It wouldn't make it appear to be empty.

Actually, that seems like a pretty key point to me.  TRUNCATE TABLE
results in a table that is perfectly valid, you just deleted all the
rows that used to be in it.  Throwing away an MV's contents should
not result in an MV that is considered valid.

It doesn't.  That was one of the more contentious points in the
earlier bikeshedding phases.  Some felt that throwing away the
contents was a form of making the MV "out of date" and as such
didn't require any special handling.  Others, including myself,
felt that "data not present" was a distinct state from "generated
zero rows" and that attempting to scan a materialized view for
which data had not been generated must result in an error.  The
latter property has been maintained from the beginning -- or at
least that has been the intent.

Yeah, "data not present" is clearly different from "0 rows".
I'm surprised there isn't an explicit boolean column somewhere
for that,

There was, in earlier versions of the patch: pg_class.relisvald.
The problem is that we needed some way to determine from the heap
that it was invalid to support UNLOGGED MVs.  Several people were
offended by my attempt to use relisvald as the primary indicator
and transfer the information from the heap state to pg_class and
relcache.  There were some pretty big technical challenges to that.
So I caved on that one and went with the pg_relation_is_scannable()
function based on the heap as reported by relcache.  That being one
of the newer parts of the patch, it is probably not as solid as the
parts which haven't changed much in the last three months.

but I guess you can use the size of the heap for that too, as
long as you're careful to not truncate it to 0 blocks when it's
empty but scannable.

There's at least one bug left in that area:

postgres=# create table t (id int4);
CREATE TABLE
postgres=# create materialized view tm as select * from t where id < 0;SELECT
0
postgres=# select * from tm;
id
----
(0 rows)

postgres=# create index i_tm on tm(id);CREATE INDEX
postgres=# cluster tm using i_tm;
CLUSTER
postgres=# select * from tm;
ERROR:  materialized view "tm" has not been populated
HINT:  Use the REFRESH MATERIALIZED VIEW command.

Clustering a materialized view invalidates it.

Good spot.  That should be easy enough to fix.

Thanks.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Andres Freund
andres@2ndquadrant.com
In reply to: Kevin Grittner (#80)

On 2013-02-21 07:10:09 -0800, Kevin Grittner wrote:

Andres Freund <andres@2ndquadrant.com> wrote:

giving an error so its an easy to find distinction to a normal
table seems like a good idea.

I'm not sure I understood your concerns entirely, but wonder
whether this helps?:

To explain it a bit:

I assume that at some point matviews will get (auto-)updateable, just as
normal views recently got. In that case application programmers might
not be aware anymore that something is a view either because they just
don't know or because a table got converted into a matview after the
code was written.

Because of the potential wish for transparency (with security views as a
potential user) at least normal views might get the capability to be
TRUNCATEd directly, so it might be that matviews do as well.

test=# \d
              List of relations
 Schema | Name  |       Type        |  Owner
--------+-------+-------------------+---------
 public | bb    | materialized view | kgrittn
 public | t     | table             | kgrittn
 public | tm    | materialized view | kgrittn
 public | tmm   | materialized view | kgrittn
 public | tv    | view              | kgrittn
 public | tvmm  | materialized view | kgrittn
 public | tvv   | view              | kgrittn
 public | tvvm  | materialized view | kgrittn
 public | tvvmv | view              | kgrittn
(9 rows)

test=# truncate table tm;
ERROR:  "tm" is not a table
test=# truncate materialized view t;
ERROR:  "t" is not a materialized view
test=# truncate materialized view tm;
TRUNCATE TABLE
test=# truncate table t;
TRUNCATE TABLE

Thats not bad.

But what if we allow TRUNCATE on views someday (possibly only if a
truncate trigger is defined). For consistency we might also want that on
matvies. Having a difference between TRUNCATE view; and TRUNCATE
MATERIALIZED VIEW; in that case sounds ugly to me.

What about DISABLE? DISCARD or DEALLOCATE would also be nice but it
seems hard to fit that into existing syntax.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Kevin Grittner
kgrittn@ymail.com
In reply to: Tom Lane (#81)

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

That being the case, lumping them as being the "same"
operation feels like the wrong thing, and so we should choose a
different name for the MV operation.

There is currently no truncation of MV data without rendering
the MV unscannable.  Do you still feel it needs a different
command name?

You didn't say anything that changed my opinion: it doesn't feel
like a TRUNCATE to me.  It's not changing the object to a
different but entirely valid state, which is what TRUNCATE does.

Peter claimed upthread that REFRESH is a subcommand of ALTER
MATERIALIZE VIEW

It's not, nor do I think it should be.

and that this operation should be another one.  That sounds
pretty reasonable from here.

That feels completely wrong to me.  For one thing, I can't think of
any ALTER commands to populate or remove data.  What did you think
of the idea of something like DISCARD MATERIALIZED VIEW DATA as a
new statment?  Or maybe RESET MATERIALIZED VIEW?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Kevin Grittner
kgrittn@ymail.com
In reply to: Andres Freund (#83)

Andres Freund <andres@2ndquadrant.com> wrote:

I assume that at some point matviews will get (auto-)updateable,
just as normal views recently got.

I'm dubious about that.  Every use case I've seen for MVs involves
aggregation, although they are a generalized feature, so that won't
always be true.  But if you have a view like:

CREATE MATERIALIZED VIEW tm AS
 SELECT t.type,
    sum(t.amt) AS totamt
   FROM t
  GROUP BY t.type;

... I don't see how that can be updateable.  If I add 5 to totamt
for some row, what do you do?  I expect that 99% of MVs will be
updated asynchronously from changes to the underlying data -- what
do you do if someone updates a row that no longer exists in the
underlying data.  This are just seems fraught with peril and out of
sync with the usual uses of MVs.

What about DISABLE? DISCARD or DEALLOCATE would also be nice but
it seems hard to fit that into existing syntax.

Thanks for the suggestions.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#84)

Kevin Grittner <kgrittn@ymail.com> writes:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Peter claimed upthread that REFRESH is a subcommand of ALTER
MATERIALIZE VIEW

It's not, nor do I think it should be.

Oh, never mind then.

and that this operation should be another one.� That sounds
pretty reasonable from here.

That feels completely wrong to me.� For one thing, I can't think of
any ALTER commands to populate or remove data.� What did you think
of the idea of something like DISCARD MATERIALIZED VIEW DATA as a
new statment?� Or maybe RESET MATERIALIZED VIEW?

I could live with either DISCARD or RESET.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87Peter Eisentraut
peter_e@gmx.net
In reply to: Kevin Grittner (#75)

On 2/21/13 9:25 AM, Kevin Grittner wrote:

Peter Eisentraut <peter_e@gmx.net> wrote:

On 2/20/13 11:14 PM, Greg Stark wrote:

That's not entirely true. From the database's point of view,
TRUNCATE is in many ways actually DDL.

Whether something is DDL or DML or a read operation (query) is
not an implementation detail, it's a user-exposed category.
Since TRUNCATE is logically equivalent to DELETE, it's a DML
operation, as far as the user is concerned.

Not really. It doesn't follow the same MVCC behavior as DML. This
is user-visible, documented behavior.

MVCC behavior does not determine whether something is considered DDL or DML.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#88Kevin Grittner
kgrittn@ymail.com
In reply to: Tom Lane (#86)

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

What did you think of the idea of something like DISCARD
MATERIALIZED VIEW DATA as a new statment?  Or maybe RESET
MATERIALIZED VIEW?

I could live with either DISCARD or RESET.

I figured this was worth a pass through the keyword list to look
for all imperative verbs suitable for this, which could support the
needed syntax without adding a new keyword.  Here are the
possibilities I came up with, along with a note about why they are
keywords already.

DISABLE MATERIALIZED VIEW mv;  -- ALTER clause for constraints
DISCARD MATERIALIZED VIEW DATA mv;  -- session state
RELEASE MATERIALIZED VIEW DATA mv;  -- savepoint
RESET MATERIALIZED VIEW DATA mv;  -- run-time parameter

I think any of these could work.  I'm personally most inclined
toward DISABLE MATERIALIZED VIEW.  It seems to convey the semantics
better, especially if you leave out DATA as an additonal word.
Given that a materialized view will retain its query, tablespace,
indexes, statistics targets, etc. with this operation, and will
just not be available for scanning, some of the above seem
downright misleading without DATA thrown in.

Opinions?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Greg Stark
stark@mit.edu
In reply to: Kevin Grittner (#76)

On Thu, Feb 21, 2013 at 2:38 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

It doesn't. That was one of the more contentious points in the
earlier bikeshedding phases. Some felt that throwing away the
contents was a form of making the MV "out of date" and as such
didn't require any special handling. Others, including myself,
felt that "data not present" was a distinct state from "generated
zero rows" and that attempting to scan a materialized view for
which data had not been generated must result in an error. The
latter property has been maintained from the beginning -- or at
least that has been the intent.

Actually this sounds like exactly what I was saying. I withdraw my
concern entirely.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#90Kevin Grittner
kgrittn@ymail.com
In reply to: Greg Stark (#89)

Greg Stark <stark@mit.edu> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

It doesn't.  That was one of the more contentious points in the
earlier bikeshedding phases.  Some felt that throwing away the
contents was a form of making the MV "out of date" and as such
didn't require any special handling.  Others, including myself,
felt that "data not present" was a distinct state from
"generated zero rows" and that attempting to scan a materialized
view for which data had not been generated must result in an
error.  The latter property has been maintained from the
beginning -- or at least that has been the intent.

Actually this sounds like exactly what I was saying. I withdraw
my concern entirely.

Reviewing your concerns and discussions of "freshness" in general
got me thinking -- while it is clear that not having generated
values in the MV based on its query clearly should make the view
non-scannable, and will be the only criterion for that in this
initial patch; later versions will almost certainly support
age-based conditions for whether the MV is scannable.  So in the
next release the MV may become non-scannable based on the passage
of time since DML was run on a source table without the MV having
been refreshed or incrementally updated to reflect that DML.  Which
makes me wonder why DML making the MV non-scannable is such a bad
thing in the case of TRUNCATE.  Granted there is a difference in
that it is run *on the MV* rather than *on a source relation*; but
still, I'm having some second thoughts about that being a problem.

The problem with TRUNCATE MATERIALIZED VIEW from a logical
perspective doesn't seem to me so much that it makes the MV
non-scannable, as that it is the only DML which would be allowed
directly on an MV -- which is kind of a weird exception.  It is
pretty much a given that when we can get to implementing it, DML
statements will render MVs unscannable under various conditions.
Josh Berkus and Greg Stark have been the most explicit about that,
but I think most of us who are interested in the feature take it as
a given.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#91Andres Freund
andres@2ndquadrant.com
In reply to: Kevin Grittner (#88)

On 2013-02-21 14:11:10 -0800, Kevin Grittner wrote:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kevin Grittner <kgrittn@ymail.com> writes:

What did you think of the idea of something like DISCARD
MATERIALIZED VIEW DATA as a new statment?  Or maybe RESET
MATERIALIZED VIEW?

I could live with either DISCARD or RESET.

I figured this was worth a pass through the keyword list to look
for all imperative verbs suitable for this, which could support the
needed syntax without adding a new keyword.  Here are the
possibilities I came up with, along with a note about why they are
keywords already.

DISABLE MATERIALIZED VIEW mv;  -- ALTER clause for constraints
DISCARD MATERIALIZED VIEW DATA mv;  -- session state
RELEASE MATERIALIZED VIEW DATA mv;  -- savepoint
RESET MATERIALIZED VIEW DATA mv;  -- run-time parameter

I think any of these could work.  I'm personally most inclined
toward DISABLE MATERIALIZED VIEW.  It seems to convey the semantics
better, especially if you leave out DATA as an additonal word.

Given that a materialized view will retain its query, tablespace,
indexes, statistics targets, etc. with this operation, and will
just not be available for scanning, some of the above seem
downright misleading without DATA thrown in.

I vote for RESET or DISCARD. DISABLE sounds more like you disable
automatic refreshes or somesuch.

Greetings,

Andres Freund

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#91)

Andres Freund <andres@2ndquadrant.com> writes:

On 2013-02-21 14:11:10 -0800, Kevin Grittner wrote:

DISABLE MATERIALIZED VIEW mv;� -- ALTER clause for constraints
DISCARD MATERIALIZED VIEW DATA mv;� -- session state
RELEASE MATERIALIZED VIEW DATA mv;� -- savepoint
RESET MATERIALIZED VIEW DATA mv;� -- run-time parameter

I think any of these could work.� I'm personally most inclined
toward DISABLE MATERIALIZED VIEW.� It seems to convey the semantics
better, especially if you leave out DATA as an additonal word.

I vote for RESET or DISCARD. DISABLE sounds more like you disable
automatic refreshes or somesuch.

Yeah, I don't much like DISABLE either. I'm also concerned about
overloading RESET this way --- that statement has complicated-enough
syntax already, not to mention way too many shades of meaning. So that
leaves me voting for DISCARD M.V. DATA, which seems pretty precise.
It's a bit verbose, but since when has SQL been succinct?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Josh Berkus
josh@agliodbs.com
In reply to: Kevin Grittner (#84)

That feels completely wrong to me. For one thing, I can't think of
any ALTER commands to populate or remove data. What did you think
of the idea of something like DISCARD MATERIALIZED VIEW DATA as a
new statment? Or maybe RESET MATERIALIZED VIEW?

I prefer RESET, especially since it could eventually support RESET ALL
MATERIALIZED VIEWS if that turns out to be useful. How does the parser
like that?

BTW, to contradict Peter E., for my part I would NOT want matview resets
to be logged as DDL. I would only want matview definitition changes to
be so logged.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#94Greg Stark
stark@mit.edu
In reply to: Josh Berkus (#93)

On Sat, Feb 23, 2013 at 1:00 AM, Josh Berkus <josh@agliodbs.com> wrote:

I prefer RESET, especially since it could eventually support RESET ALL
MATERIALIZED VIEWS if that turns out to be useful. How does the parser
like that?

Isn't reset currently only used for GUCs? I think that makes for a
strange crossover.

Really, I'm sorry for bringing this up. I really don't think
"truncate" is a bad way to spell it.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#95Michael Paquier
michael.paquier@gmail.com
In reply to: Greg Stark (#94)

On Sat, Feb 23, 2013 at 9:55 PM, Greg Stark <stark@mit.edu> wrote:

On Sat, Feb 23, 2013 at 1:00 AM, Josh Berkus <josh@agliodbs.com> wrote:

I prefer RESET, especially since it could eventually support RESET ALL
MATERIALIZED VIEWS if that turns out to be useful. How does the parser
like that?

Isn't reset currently only used for GUCs? I think that makes for a
strange crossover.

it is. http://www.postgresql.org/docs/9.2/static/sql-reset.html
DISCARD would be better.
My 2c.
--
Michael

#96Erik Rijkers
er@xs4all.nl
In reply to: Kevin Grittner (#55)

2013-02-19 Kevin Grittner wrote:
[matview-system_views-v2.diff]

I assumed the patches matview-v4.patch and matview-system_views-v2.diff
were to be applied together.

They do apply correctly but during tests, the "test rules ... FAILED".

Perhaps it is solved already but I thought I'd mention it in case it is overlooked.

Thanks,

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#97Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#95)

On Sat, Feb 23, 2013 at 8:00 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

it is. http://www.postgresql.org/docs/9.2/static/sql-reset.html
DISCARD would be better.

Well, personally, I'm in favor of either TRUNCATE or ALTER
MATERIALIZED VIEW ... DISCARD. I think it's a dangerous precedent to
suppose that we're going to start using DISCARD for things that have
nothing to do with the existing meanings of DISCARD. Number one, I
think it's confusing. Number two, it's currently possible to
determine whether something is DDL, DML, or other by looking at the
first word of the command. If we throw that out the window we may
cause performance issues for connection pooling software that tries to
be clever like that. Mind you, I'm not aware of any connection
pooling software that actually does this today, because I think pgpool
includes a full parser and pgbouncer includes no parser at all, but it
still seems like a possibly-useful trick. And number three, the
possibility of grammar conflicts with things we might want to do in
the future seems unnecessarily high. If we use ALTER MATERIALIZED
VIEW ... WHATEVER, we only have to worry about grammar conflicts with
other ALTER MATERIALIZED VIEW commands; if we reuse DISCARD or RESET,
we've got potential conflicts with completely unrelated syntax. That
consideration alone would be sufficient reason for me to choose to
stick everything under ALTER MATERIALIZED VIEW, no matter how poor a
semantic fit it seems otherwise. Of course, if we stick with
TRUNCATE, this becomes a non-issue.

All that having been said, I'm not in favor of pushing this patch out
to 9.4 because we can't agree on minor syntax details. In the absence
of consensus, my feeling is that Kevin should exercise committer's
prerogative and commit this in the way that seems best to him. If a
clear contrary consensus subsequently emerges, we can always change
it. It is not as if the particular choice of SQL syntax is hard to
whack around. Once we release it we're stuck with it, but certainly
between now and beta we can change it whenever we like. I'd rather
have the core part of the feature committed and tinker with the syntax
than wait longer to land the patch.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#97)

Robert Haas <robertmhaas@gmail.com> writes:

On Sat, Feb 23, 2013 at 8:00 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

it is. http://www.postgresql.org/docs/9.2/static/sql-reset.html
DISCARD would be better.

Well, personally, I'm in favor of either TRUNCATE or ALTER
MATERIALIZED VIEW ... DISCARD. I think it's a dangerous precedent to
suppose that we're going to start using DISCARD for things that have
nothing to do with the existing meanings of DISCARD.

Yeah, there's actually a serious problem with choosing DISCARD:
surely we are not going to include "trash all MVs" in the behavior
of DISCARD ALL. So unless you would like to say that DISCARD ALL
doesn't mean what it appears to mean, we can't make MV reset be
one of the sub-flavors of DISCARD.

So that seems to leave us with either TRUNCATE or an ALTER sub-syntax.
Personally I'd prefer the latter but it's surely debatable.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#99Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Robert Haas (#97)

On 28.02.2013 16:55, Robert Haas wrote:

On Sat, Feb 23, 2013 at 8:00 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

it is. http://www.postgresql.org/docs/9.2/static/sql-reset.html
DISCARD would be better.

Well, personally, I'm in favor of either TRUNCATE or ALTER
MATERIALIZED VIEW ... DISCARD. I think it's a dangerous precedent to
suppose that we're going to start using DISCARD for things that have
nothing to do with the existing meanings of DISCARD. Number one, I
think it's confusing. Number two, it's currently possible to
determine whether something is DDL, DML, or other by looking at the
first word of the command. If we throw that out the window we may
cause performance issues for connection pooling software that tries to
be clever like that.

FWIW, I totally agree with that. From that point of view, the best thing
would be to tack this onto the REFRESH command, perhaps something like:

REFRESH matview INVALIDATE;
REFRESH matview UNREFRESH;
REFRESH matview DISCARD;

It's a bit weird that the command is called REFRESH, if the effect is
the exact opposite of refreshing it. And we usually do have two separate
commands for doing something and undoing the same; CREATE - DROP,
PREPARE - DEALLOCATE, LISTEN - UNLISTEN, and so forth.

I think we're being too hung up on avoiding new (unreserved) keywords.
Yes, the grammar is large because of so many keywords, but surely
there's some better solution to that than adopt syntax that sucks. Let's
invent a new keyword (INVALIDATE? UNREFRESH?), and deal with the grammar
bloat separately.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#98)

On Thu, Feb 28, 2013 at 11:00 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, personally, I'm in favor of either TRUNCATE or ALTER
MATERIALIZED VIEW ... DISCARD. I think it's a dangerous precedent to
suppose that we're going to start using DISCARD for things that have
nothing to do with the existing meanings of DISCARD.

Yeah, there's actually a serious problem with choosing DISCARD:
surely we are not going to include "trash all MVs" in the behavior
of DISCARD ALL. So unless you would like to say that DISCARD ALL
doesn't mean what it appears to mean, we can't make MV reset be
one of the sub-flavors of DISCARD.

Good point.

So that seems to leave us with either TRUNCATE or an ALTER sub-syntax.
Personally I'd prefer the latter but it's surely debatable.

I agree.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#101Kevin Grittner
kgrittn@ymail.com
In reply to: Heikki Linnakangas (#99)

Heikki Linnakangas <hlinnakangas@vmware.com> wrote:

On 28.02.2013 16:55, Robert Haas wrote:

Well, personally, I'm in favor of either TRUNCATE or ALTER
MATERIALIZED VIEW ... DISCARD.  I think it's a dangerous
precedent to suppose that we're going to start using DISCARD for
things that have nothing to do with the existing meanings of
DISCARD.  Number one, I think it's confusing.  Number two, it's
currently possible to determine whether something is DDL, DML,
or other by looking at the first word of the command.  If we
throw that out the window we may cause performance issues for
connection pooling software that tries to be clever like that.

FWIW, I totally agree with that. From that point of view, the
best thing would be to tack this onto the REFRESH command,
perhaps something like:

REFRESH matview INVALIDATE;
REFRESH matview UNREFRESH;
REFRESH matview DISCARD;

It's a bit weird that the command is called REFRESH, if the
effect is the exact opposite of refreshing it. And we usually do
have two separate commands for doing something and undoing the
same; CREATE - DROP, PREPARE - DEALLOCATE, LISTEN - UNLISTEN, and
so forth.

I think we're being too hung up on avoiding new (unreserved)
keywords. Yes, the grammar is large because of so many keywords,
but surely there's some better solution to that than adopt syntax
that sucks. Let's invent a new keyword (INVALIDATE? UNREFRESH?),
and deal with the grammar bloat separately.

I'm OK with any grammar that we can reach consensus on, but that
seems elusive and I don't want to hold up getting the meat of the
patch committed while we haggle out this syntax detail.  Votes have
shifted frequently, but as I make out the latest opinion of people
making a statement that looks like an explicit vote, dividing the
value of a split vote between the choices, I get:

Kevin Grittner: TRUNCATE
Stephen Frost: TRUNCATE
Peter Eisentraut: ALTER
Andres Freund: RESET or DISCARD
Josh Berkus: RESET
Greg Stark: TRUNCATE
Michael Paquier: DISCARD
Robert Haas: TRUNCATE or ALTER
Tom Lane: TRUNCATE or ALTER
Heikki Linnakangas: REFRESH?

Vote totals:

TRUNCATE:  4.0
ALTER:     2.0
DISCARD:   1.5
RESET:     1.5
REFRESH    1.0?

Barring a sudden confluence of opinion, I will go with TRUNCATE for
the initial spelling.  I tend to favor that spelling for several
reasons.  One was the size of the patch needed to add the opposite
of REFRESH to the backend code:

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2a55e02..eb7a14f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1234,11 +1234,12 @@ truncate_check_rel(Relation rel)
 {
    AclResult   aclresult;
 
-   /* Only allow truncate on regular tables */
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
+   /* 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",
+                errmsg("\"%s\" is not a table or materialized view",
                        RelationGetRelationName(rel))));
 
    /* Permissions checks */

That's it.  That takes it from "no way to release the space held by
the current contents of a materialized view and render it
unscannable until its rule's query is used to populate it again" to
"working".  Now, there are docs and psql support needed on top of
that, and the regression tests use the verb, and bikeshedding led
to a minor tweak of TRUNCATE so that you could specify TRUNCATE
MATERIALIZED VIEW -- but to get working backend code, the above is
sufficient.  That strikes me a prima facie evidence that it's not a
horribly off-target verb.

In terms of things to consider in choosing a verb are that, while
complete absence of data in the MV's backing table is the only
thing which will render it unscannable in this initial version,
there is clear demand to eventually track information on how
up-to-date data is, and treat the MV as unscannable for less
extreme conditions, such as the asynchronous application of changes
from backing tables having fallen behind some configurable
threshold.  In essence, people want the error instead of scanning
the relation if the generated data is not within a range of time
that is "fresh" enough, and truncating the backing relation (or
creating the MV WITH NO DATA) is a special case of that since the
data is not representing the results of the MV's query as of *any*
known point in time.  (As previously discussed, that is distinct
from an empty relation which is known to represent a fresh view of
the results of that query.)

If we pick a new pair of verbs, the connotations I think we should
be looking for include the ability to deal with at least these:

* Make the MV represent the result set of its query as of this
moment.

* Declare the MV to be too stale for the contents of its backing
table to be useful, and free the space held by the current
contents.

Other operations will be needed, for which syntax is not settled.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Ants Aasma
ants@cybertec.at
In reply to: Kevin Grittner (#101)

On Thu, Feb 28, 2013 at 7:52 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

Barring a sudden confluence of opinion, I will go with TRUNCATE for
the initial spelling. I tend to favor that spelling for several
reasons. One was the size of the patch needed to add the opposite
of REFRESH to the backend code:

FWIW, I found Andres's point about closing the door on updatable views
quite convincing. If at any point we want to add updatable
materialized views, it seems like a bad inconsistency to have TRUNCATE
mean something completely different from DELETE. While update
capability for materialized views might not be a common use case, I
don't think it's fair to completely shut the door on it to have easier
implementation and shorter syntax. Especially as the shorter syntax is
semantically inconsistent - normal truncate removes the data,
materialized view just makes the data inaccessible until the next
refresh.

Sorry for weighing in late, but it seemed to me that this point didn't
get enough consideration.

Ants Aasma
--
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#103Kevin Grittner
kgrittn@ymail.com
In reply to: Ants Aasma (#102)

Ants Aasma <ants@cybertec.at> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

Barring a sudden confluence of opinion, I will go with TRUNCATE
for the initial spelling.  I tend to favor that spelling for
several reasons.  One was the size of the patch needed to add
the opposite of REFRESH to the backend code:

FWIW, I found Andres's point about closing the door on updatable
views quite convincing. If at any point we want to add updatable
materialized views, it seems like a bad inconsistency to have
TRUNCATE mean something completely different from DELETE. While
update capability for materialized views might not be a common
use case, I don't think it's fair to completely shut the door on
it to have easier implementation and shorter syntax. Especially
as the shorter syntax is semantically inconsistent - normal
truncate removes the data, materialized view just makes the data
inaccessible until the next refresh.

Sorry for weighing in late, but it seemed to me that this point
didn't get enough consideration.

Personally, I don't understand why anyone would want updateable
materialized views.  That's probably because 99% of the cases where
I've seen that someone wanted them, they wanted them updated to
match the underlying data using some technique that didn't require
the modification or commit of the underlying data to carry the
overhead of maintaining the MV.  In other words, they do not want
the MV to be up-to-date for performance reasons.  That is a big
part of the reason for *wanting* to use an MV.  How do you make an
asynchronously-maintained view updateable?

In addtion, at least 80% of the cases I've seen where people want
an MV it is summary information, which does not tie a single MV row
to a single underlying row.  If someone updates an aggregate number
like an average, I see no reasonable way to map that to the
underlying data in a meaningful way.

I see the contract of a materialized view as providing a
table-backed relation which shows the result set of a query as of
some point in time.  Perhaps it is a failure of imagination, but I
don't see where modifying that relation directly is compatible with
that contract.

Can you describe a meaningful use cases for an udpateable
materialized view?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#104Ants Aasma
ants@cybertec.at
In reply to: Kevin Grittner (#103)

On Fri, Mar 1, 2013 at 4:18 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

Personally, I don't understand why anyone would want updateable
materialized views. That's probably because 99% of the cases where
I've seen that someone wanted them, they wanted them updated to
match the underlying data using some technique that didn't require
the modification or commit of the underlying data to carry the
overhead of maintaining the MV. In other words, they do not want
the MV to be up-to-date for performance reasons. That is a big
part of the reason for *wanting* to use an MV. How do you make an
asynchronously-maintained view updateable?

In addtion, at least 80% of the cases I've seen where people want
an MV it is summary information, which does not tie a single MV row
to a single underlying row. If someone updates an aggregate number
like an average, I see no reasonable way to map that to the
underlying data in a meaningful way.

I see the contract of a materialized view as providing a
table-backed relation which shows the result set of a query as of
some point in time. Perhaps it is a failure of imagination, but I
don't see where modifying that relation directly is compatible with
that contract.

Can you describe a meaningful use cases for an udpateable
materialized view?

I actually agree that overwhelming majority of users don't need or
want updateable materialized views. My point was that we can't at this
point rule out that people will think of a good use for this. I don't
have any real use cases for this, but I can imagine a few situations
where updateable materialized views wouldn't be nonsensical.

One case would be if the underlying data is bulkloaded and is
subsetted into smaller materialized views for processing using
off-the-shelf tools that expect tables. One might want to propagate
changes from those applications to the base data.

The other case would be the theoretical future where materialized
views can be incrementally and transactionally maintained, in that
case being able to express modifications on the views could actually
make sense.

I understand that the examples are completely hypothetical and could
be solved by using regular tables. I just have feeling that will
regret conflating TRUNCATE semantics for slight implementation and
notation convenience. To give another example of potential future
update semantics, if we were to allow users manually maintaining
materialized view contents using DML commands, one would expect
TRUNCATE to mean "make this matview empty", not "make this matview
unavailable".

Ants Aasma
--
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#105Greg Stark
stark@mit.edu
In reply to: Ants Aasma (#104)

On Fri, Mar 1, 2013 at 3:01 PM, Ants Aasma <ants@cybertec.at> wrote:

. To give another example of potential future
update semantics, if we were to allow users manually maintaining
materialized view contents using DML commands, one would expect
TRUNCATE to mean "make this matview empty", not "make this matview
unavailable".

Wouldn't that just be a regular table then though? How is that a
materialized view?

If anything someone might expect truncate to delete any rows from the
source table that appear in the view. But I think it's likely that
even if materialized views were updateable truncate wouldn't be one of
the updateable operations.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#106Kevin Grittner
kgrittn@ymail.com
In reply to: Greg Stark (#105)

Greg Stark <stark@mit.edu> wrote:

Ants Aasma <ants@cybertec.at> wrote:

To give another example of potential future update semantics, if
we were to allow users manually maintaining materialized view
contents using DML commands, one would expect TRUNCATE to mean
"make this matview empty", not "make this matview unavailable".

Wouldn't that just be a regular table then though? How is that a
materialized view?

If anything someone might expect truncate to delete any rows from
the source table that appear in the view. But I think it's likely
that even if materialized views were updateable truncate wouldn't
be one of the updateable operations.

Yeah, the only way it would make sense to truncate MV contents from
a user-written maintenance trigger (assuming we might have such a
thing some day) would be if you decided that the change to the
underlying data was so severe that you effectively needed to
REFRESH, and there would probably be a better way to go about
dealing with that.

Two other reasons that this might not be a problem are:

(1)  Any DML against the MV would need to be limited to some
context fired by the underlying changes.  If we allow changes to
the MV outside of that without it being part of some "updateable
MV" feature (reversing the direction of flow of changes), the MV
could not be trusted at all.  If you're going to do that, just use
a table.

(2)  I can think of a couple not-too-horrible syntax tricks we
could use to escape from the corner we're worried about painting
ourselves into.

All of that said, some combination of Heikki's previous suggestion
that maybe REFRESH could be used and my noticing that both TRUNCATE
and REFRESH create a new heap for an MV, it's just a question of
whether we then run the MV's query to fill it with data, led to
this thought:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

This sort of mirrors the CREATE MATERIALIZED VIEW style (which was
based on CREATE TABLE AS) and WITH NO DATA puts the MV into the
unscannable state either way.  I can change the parser to make this
literally just the new spelling of TRUNCATE MATERIALIZED VIEW
without dashing my hopes of pushing the patch tomorrow.  (The patch
has been ready to go for weeks other than this syntax issue and
documentation which needs to refer to whatever syntax is chosen.)

Barring objections, I will use the above and push tomorrow.

The only issues which have been raised which will not be addressed
at that point are:

(1)  The suggestion that ALTER MATERIALIZED VIEW name ALTER column
support changing the collation isn't something I can see how to do
without complication and risk beyond what is appropriate at this
point in the release cycle.  It will be left off, at least for now.
 To get a new collation, you will need to drop the MV and re-create
it with a query which specifies the collation for the result
column.

(2)  The sepgsql changes are still waiting for a decision from
security focused folks.  I have two patches for that contrib module
ready based on my best reading of things -- one which uses table
security labels and one which instead uses new matview labels.
When we get a call on which is preferred, I suspect that one patch
or the other will be good as-is or with minimal change.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#107Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Kevin Grittner (#106)

On 2 March 2013 15:06, Kevin Grittner <kgrittn@ymail.com> wrote:

[ ... ] led to
this thought:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

[Sorry to join this discussion so late]

FWIW I had a quick look at other DBs to see if there were any other
precedents out there. Oracle was the only one I could find with
anything similar. They use the same creation syntax:

CREATE MATERIALIZED VIEW name [options] AS SELECT ...

and they use ALTER for everything else, such as refreshing the MV:

ALTER MATERIALIZED VIEW name REFRESH [options];

AFAICT the nearest thing they have to TRUNCATE/DISCARD is:

ALTER MATERIALIZED VIEW name CONSIDER FRESH;

They do also support updateable materialized views with standard DML,
but it doesn't look as though they allow TRUNCATE to operate directly
on a MV (although it can be made to propagate from a base table to a
MV, in which case allowing TRUNCATE on the MV itself with a different
meaning would likely be confusing).

Oracle's MVs have lots of options, all of which hang off the 2 basic
CREATE and ALTER commands. There's a certain appeal to that, rather
than inventing or overloading a bunch of other commands as more
options get added. The proposed REFRESH command is OK for today's
options, but I think it might be overly limiting in the future.

Of course, since this isn't in the SQL standard, we are free to use
any syntax we like. We don't have to follow Oracle, but having a
common syntax might make some people's lives easier, and I haven't
seen a convincing argument as to why any alternative syntax is better.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#108Kevin Grittner
kgrittn@ymail.com
In reply to: Dean Rasheed (#107)

Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

[ ... ] led to this thought:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

[Sorry to join this discussion so late]

FWIW I had a quick look at other DBs to see if there were any
other precedents out there. Oracle was the only one I could find
with anything similar. They use the same creation syntax:

   CREATE MATERIALIZED VIEW name [options] AS SELECT ...

It is a pretty obvious choice when you look at other SQL
statements.

and they use ALTER for everything else, such as refreshing the
MV:

   ALTER MATERIALIZED VIEW name REFRESH [options];

No, that is for specifiying when and under what conditions an
automatic refresh is done.  To do an immediate action which is
equivalent to what I have for the REFRESH statement, they use a
REFRESH() function.  That seemed too incompatible with how we've
done everything else in PostgreSQL -- I felt that a statement would
make more sense.  Consider REINDEX, CLUSTER, and VACUUM FULL for
example.

AFAICT the nearest thing they have to TRUNCATE/DISCARD is:

   ALTER MATERIALIZED VIEW name CONSIDER FRESH;

No, that doesn't rebuild or discard data -- if the MV is
out-of-date and therefore unscannable according to the how the MV
has been set up, this overrides that indication and allows scanning
in spite of that.

They do also support updateable materialized views with standard
DML, but it doesn't look as though they allow TRUNCATE to operate
directly on a MV (although it can be made to propagate from a
base table to a MV, in which case allowing TRUNCATE on the MV
itself with a different meaning would likely be confusing).

They allow DML on the MV in order to update it.  The default
REFRESH() function executes a TRUNCATE statement followed by INSERT
/ SELECT using the MV's query.

Oracle's MVs have lots of options, all of which hang off the 2
basic CREATE and ALTER commands. There's a certain appeal to
that, rather than inventing or overloading a bunch of other
commands as more options get added. The proposed REFRESH command
is OK for today's options, but I think it might be overly
limiting in the future.

For what ALTER MATERIALIZED VIEW in Oracle does, I think it makes
sense to use ALTER.  I don't think this feature should use
functions for REFRESH.  Why Oracle can get away with functions for
it is that they allow DML on an MV, which seems to me to compromise
the integrity of the feature, at least as default behavior.

I see us supporting automatic incremental updates of progressively
more complex queries, and we may at some point want to add a
trigger-based maintenance option; but the functionality available
with a trigger-based approach is almost entirely availaable in
PostgreSQL today without this feature.  Rewriting queries using
expressions which match the MV's query to pull from the MV instead
of the underlying tables is the exception.  While that is a "sexy"
feature, and I'm sure one can construct examples where it helps
performance, it seems to me unlikely to be very generally useful.
I suspect that it exists mostly so that people who want to write an
RFP to pick a particular product can include that as a requirement.
 In other words, I think the main benefit of automatic rewrite
using an MV is marketing, not technical or performance.  That's
important, too; but let's focus first on getting what is most
useful.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#109Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#106)
1 attachment(s)

Kevin Grittner <kgrittn@ymail.com> wrote:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

Given the short time, I left out the "[, ...]".  If people think
that is important to get into this release, a follow-on patch might
be possible.

Barring objections, I will use the above and push tomorrow.

I'm still working on docs, and the changes related to the syntax
change are still only lightly tested, but as far as I know, all is
complete except for the docs.  I'm still working on those and
expect to have them completed late today.  I'm posting this patch
to allow a chance for final review of the code changes before I
push.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

matview-v5.patch.gzapplication/x-gzip; name=matview-v5.patch.gzDownload
�]3Qmatview-v5.patch�=kw�6���_�����,�!�E����j��Ii�7��P$$qC�,A�Q����;�oP��G����-��`f0@�x��hU��}�U�����>T�g;;;d�����?�Nj�Z�v�K�a�YC���C#���e�����t�����,�{����M�f�~b���� �ln\���?��q'C]�5��T�#�\M��&��+@yC��G��m�#�X����#�Qb ��P%����Q��:���1�4>1I>��?�>��������
D�����l�4;�	2`��
_N���o%o�"K�Uo�]2�=��r��w��-�����W�����{�E�����[�����W��$���@V�m��L������YwJR��[�*
����L%���%�^�X�"�=!9�H�'�L�N<��U�;9��oW���������X����!�g�3+`>������R&�#��a�����^�9h�A����k�~!���-�}%#%�	l��I�u�:�B ��y��u/3��-�1�/S��u`�AQ2��F��
�-e�/�
7(q�L�q<w��� >��D�����R*���\M����7*�n��.PJ=����(�PJ����p|�d�^������\�����A=�����s~��W�`�k����n��,��|�>��������}�������s��
�@7�=���#��C�%�plkN�Ts��t���<J�5=f ���$F.�����`n��t,c(�I�3�}��+n������r�c�6��O�?�H2/Z��Oe2�`F�f��{�MzS�+�f��B����Pk���w��J��j���0��]����_�c���7��0`��*��6�@�I��7_J)��� ��Z�%=���A��_
��$��a��v�d>�`rR9��!6����r�7
5�[bH!}n@��A����
)�e��U�*j�E�Nw z�����Fl�r:�?(�����Q`�g�]S�G9��\��s��
�����K��%V��@b��n��66~]]h�����x�Mr��e���N��r�D������/p-�����E��Q��Qy?��Ew�5��K=���2y�:~;�,�+�X�<�39�������<��ix�P����9tL�z���;�C�����W\�������v���<��?7/>��������lG��N�o��LN�u����ZN�!�kU%�wO��{���a_t����T� )�)h���rc�j�6# ��-���Q0.���p��J��]�v"���� x���
�`X>�-�H�r �E���G����QX.�<���������h3��t�z�����sx�k���W���O��R(��~����2����+�p�*������J��*�M.��a���qy�h��W�3>���	I�b������W��,�1��9��(D�sj�y0%�T��T��X��f�M~�S������3����C(8�
�M�g����Bf
�
r6�Us@��/C3���y|�x��)+O�Vb)�	^��\q� ����%G��2B\�6*��!����fJmrC��j6Pgg����^���3�D
�2�j_�Y����i#��r���q|�&��\���>��@D�G���*"��$.��_V��xF�)P�?�(����iy���(a�����Q������wR��]�f5<��;�3L[���a����\Dr�����=�V�Z��t>z���1���}�cYA�X��Qsb�~��3l�W=��!V���qbU=a�)g�9�u��+��-x���c��=`������Z��?OE$)�fqf44����������A*3�*7�1����W�`/p�\B\�	�W��`�3��|�x�R<q=s�F�.W���m#_�����H�}I���XY�H�3N R�f��������;�)�-e�r��zQ����:��A��:��)�j�#���|����jZ�F���/�j�0�z��z>��"Hl��9��:(�|BC��^
����^$���t���3Y���G�wRp����i����P����  �n����X�FH#���}#F|W��xt�i�Pn��O�O�cT��e��������W��8W��A����d;����a�O�9lBOv�!�>R�����L���MB����w�^lRLX�uel������x����L����m�;->K�};����'���{
f��T������_�r>.����p�9ly���:Qu�y��y����=���{��O���y)�SJ\
�`�����(as������q2qb�Y7��_��_e��a��p;����>��������s�D��a���pO7SS���^����R���|cZ�|H[�a%�^����z���$�4{~g%5*��"M��#�	��	��k�
hR��[WW5�B�+.��;)�{�}�yK�X�0k04�GZ��&K�x��t&���2��+�+��\��62��[�A{��,~~��_#��Z�d�7�mwN�f#x���@�P�%��a�	��
r,�
��y�2�P��QS�(���}S]�����#�����YX^eY�V���<;��C`�n��N�I7�j����n�&�������hN�M1C��R�d�Z���^e �z�2\��*�!��T#�e�j����A)G8��r����/���������{{y��8)���M�
��i���+RlL�4����B����(vL[�4�j�J�-��$���,�

qpT�����B����B@��b�K���M�������L��<4��6X���{�G�S��MC�z���lZ��}%����V�$E:
��H��5u��S@�i��6%8�>!�1�)��H��l2�8`Q�U�(GM>!��A�GZ��:�v� u-����q��y��5/[�o�!6x}�������aJ�go��V�]��7M������!�$���b�?R8����"��9�0�0�Ua*�O�?�( �l@��GN3��WT��y0L��>�I�R�����/�0�v���?�s�G��(�g�t�YM%?��Zt���+/�3'S��������f���f}��;������!|�:�R3#j�6/��|Kr�ZT
z]�k�	R�b�)�#���R�j����n7�Kr�0����
f��4�vvd��0��!1�7��u����%����D����\�3�x�u�nt����e��q����L��6���QxF��D�u��U�����
�:��Oc-��w�����:�6�)�@E�M�
���f�v+�e	J���������M�����(6%�Q�B���2G2p
ln;.3��~�/x����#l�����.������w"+��du?v���tF�1����GY�����HK�F��G������O���*��=�G%��Nl��E���7�A ���>G�K�TE�e�IR��S�����B*������6n9�s$�E�$3>�������r[",Ez8h������t�X��O�Z I�����T28����Wh\Z��;�Ej����@��^�]���\]4����z�����9�e��Bh�_T�j��	��q�����qO�_��S�{��f�H
+/<��
�u\���#��g:#Z���Lp���a6����1x5=G�/N@f�����R�Z�;$`t�� �|I���-F��/�y88�'S��J�e��<}���i�R%�����#�������r��`���=~����g4�UJ�!��2����f���B~8��<�������;�o�����X3�x�
��w���|�@T��O�v��z��`9G�i���N`�a4�{n.��l`����_����B~rn������
t�#:�#������F�$3d |�������E����t:+�[M�p��V_���~M���<������g�-�5z�ZAD�J�W�J��i�5�R��-vb�|[0H\AE�,�4������jv��9�eZ$k���������sn�|<���z�jr>�
i�S�><�X��b[�q>$/����P	�R�^��E.X��]�<����:&�'��M��$U�������D���7u�!_��q������m�'1E\���9&q[-9���)��]�����U��W�9&�����\vj�_qr�D��;�@l�yq�+�z��m	g�(w��m�����k�X��f�?�����&��@#��o�r���$.���)%MOs`x:��?��f���@�f8)�6�_-��BmCl��zR�b��^�0K1`�xxY`\���h�
��{���>�5����Tei��\;�E���e�3 ��(��`�/.���k^��6����k���������?�����U���8Q�A�w%��J�������?>-����+?^���0\����3H���n�v�l��e�9�9H'��o�z-_����B��@-��*QKd����w�)��RB�0:sp����i$������Z.d���\��������0��v����S#����X����v���|�#,n��N���	�=�*C?�a���1�3 �����d)�T���a��e�?���������zXL�o�-7��)u��V��
^����>3��R,�9.��7�=`�����!V�����!�������P�=<^V�]<���_���F�W�|��(�.$V]��_�c�\���E���T����;�~%�O�C���������6�Eu��}J��{�a�����c{����P�a�V��-��$+x	��y��U�l������j�!�f�q7�/8����t�@?+���#W1�/����`������>��H��g����Y
�g0q��u���|��r,����>�Y�9^{��K�m������!���]q�m�� mNF��h�A����^�m���i��bn�Q�3~�\��k�{V��rd����`�z�GE.&.����%���pYE��/M�``D��q�KiY;+����p�"�^��[n����E�r�F
�8��r&jD{B����3�0Q�Fa��C��{���BC�`���(�����B�s�mR�5����*\�dG��@�_���	D���!�n��_��{�L�
�4�gaO����z"y��}�e�p�3�2����pm�G�b��@�_��p��H�;1��#�<V��FR���P#~�����&���%P)Q<�:�S8uuXz�iq'��bK�T���][^D-4��dM"�	���$�w ����I��N�g�?��U���IS���Z8l6���/Q�T'��\�pb���������G����D����{qJ$��B!�e #Ei�I9yh��_t�_���v=�[�>tR[��)��j"W�QUL��xx%�Q����8��eG�+��X�E����Q�po	�n��o|����c|9P`����#<�=�!�(�;To_[-���+c���]
[	���~�u�g����n�H)���	P�U��j����|�c����vlZ36������n����;������v������(.�i�|���`���v��#���b���=���]��}p|Z>��/B�����G�?��!n>���!�������>�`�z�r����+#�j�����[����F!�������Tu��3�Zy�z����hB��c�A��������'���g���/4z�������.�?��YtxX`;J'�1�gieV�����q��\��q�����}jy���o���&�1T�x���f�;������*���O�
���
��(�s�}
Id��������*�3��6���c�'4ik3�G�����`�����r.f������\6�j4nL=���I��w�cU�(������!a��-�t)$����R�������;o0<X��4X�R��9����`��frSn�D`A��	���-J8��7(��tJ���;f��o�
<v���A��@��Xlt|�����w@�9@��y���4�~N���-�'�r|���������S|�o"�e�l������{��7/>�9�Eq~�_4�����o���7�������p�	f��e�$���g�/&���&y�[����#�0��|����VKjyf��[��������]&�����S�h��|qTW����i+�0������~w�W6x,�ufqXZn�/=�A��A{"�&Us�F����h��uz��_`p���zV��!�+>2!~o�c>M�DD�u(�)�q��u�����/�����p'K/LE��e���a��{����sb��W�xa2��.�]�tA��l����D������~��z����y�}�Vn4�g�uMsI��5[1oP`����l_�%L����k����_�5��8����<�����/��}m�{��$�R�a�*��8��V��Ze�����E	�x��g2����9`w�NzJ����W�&U��R������*�����/��I�����La�����d�B�^,�J�j��-m*�������Z�T�H~��-�7�@�rJ�9Br\����g?�H���M�&0�g�h��!�5��Y�wD��_��	8
�^�����s']�T��u�������S����V����N����d
[[x�B�����GC��F��<��r��q��%�l�WS������%�a}c]���1
D_��t_�-��������3	9U'�pk��E�{��|nF�R��%3=_;��}a�V���01�E�c�AAh��-6cn����
�;�XOw�`-����^4��8�C��)Ghc+����Z���>�5t�H�c3�Kv��~9�e�!Y����r�j������2��7��Y���027�m%6l��b��w�3oYO�x*-si'�5.�L�+�\��=��*89��e�8��rN�_��������	y�xx!���>z��Q<Dov���>�pS��zbmy�VA���1��Q�z�M�v��ha�=���B�-2���#}�K���w�N�\�sl�`�(0~������I�+���QY��^0����m�UIl���M�,�m�%��LJ�D/�)Knq}��n��o�k���$ 2w��^�Z��Q��M	~���tTo�M�����.� �>��:�1�Y���!�+�>m7�p�R���JMd_��%�C?����G���$+tu��(����k|�@�|��~t�"�?.(
C�6��@��h�7�'���",����[���DQ���U�J]�*:��;'!/���%�E��-���x����mK>|J����B\�&7p��c�7;�aoD%����������DG�D��ppMnCc�y�SK��aj�f�}-��1v�����-:{Xc?W`�6����Bqu��X@����H�D	���Ak��'mE���W��������]�����������,�:F���������|!��b}�Z���R��|���(x��`������W@�'���F>�)bB�F�f7��,���g}�+�]o^��8bU��2�^��l�o����9����W�[��J�69FVOml.��,���H�2�)6�>����t<��
��5:��wg�"�}���K�!]Jm8K.+4b�(�V��lt�����D�\������45��B?�����K_�^X	��3
�v*
�0��l�-����6]��#�������Cx�������U�do���=C�{�4���7g����3�r&4�K{���zh���/��K�����L�����fUP��x|���u%.�������CR-So\9&�����IlG��	���}�T�+��(���d��L�����kw:���eARk��z�� ����:Z1����;0�����;{'|�����M�+�#��g���cN����D�����R���4���\8�a�����F�,R�?���9_z��|~8c�h�J@��Q�\�� Q���}
Y�+���P5W��2��w~8��v$��2�3�Jn���$):Y@y�� ���+)�:e��cvn�bE�y�(��iw����<�zf�>��2��V����x�[Ml�/���������ewS�W	h���MX,b����i��������H�`l�X�3��%9!�P9��e�-�����\��W{��v�Q4�8<	)@�)����z%��9�,��������.��$b`�'�;��m�S��A����_)�V�������]8������=���������&8��X��9-��!�b}�o'��
�&>���W_�9F����>�M4.��fk^�j�Q*�5���U*�
�d�J���b��$�g������U�cL�$=����d�B�"��[!za���h
���H:l�(�AR���!%�X��E�����w������Q����YgB�����h�����&��$����<��;w�~����p�PF\�!�SP����TdM��~��j��.�S���P/�����|dC�8>�)���O����
��l
�`��6�a��]�%��v�Z%��/�O����h����g��^�������B;7���l���)!0@��F��d�)�Pw.����$+����pg<��l��W��}HoV����NR�*��e�+���\����+jlb���J��y������ii@<6��Q����l	',�`xq7T�d�XP3�s5����4���KZ\
N>���dK�rEu�`^���G��6nh��1���"q�u�"A5�������S\�K _�"L�i�8��:�^��2
0��?v��a��3���=}-�0������P�mMY�&���%�6 ���X�8��w���&�&��tY��+�B��� �Z&��gm����d��cY3m~g��9;��1s��
���9g�y�K����d�)9x���%D1S�����je��1��(OP�T*k���s�y5��_��4�Aq)�ap�r�� N�vW���w�^�_2��vJ!��^z0�'�p�a���)�2�:=�s��.jLf�"��,"�L�������p�C����\�Z��A8����fz�vB�+�b�����*H#!����`$�
�� �����������������z�7J�f&�Q���SZ�S}�c���}�`/
��0!�\����Hr��L�j��%#�g��9G�1M�XL���#��y�d]�n�����}������W���q����Yx��N#�j�$|� �V
$�p���2�]�X���4��K-�\�m�&��|�}�����R���9l�;8�S��S�<��;4����1�1c�Y�2���{y���t6������j�bj����]�p�p���������f�26#������w39��vU�pw�L&[���R=%=��p���^x�VX�����.�{��3��/<
;&�v�h>~�9�4�'�j9!|.��m���cD�2 Zy	������H�6�h@��=�%c��z��6*�8��F����0��q�A���a�j�^�g�=P���;!�I��h��l_>�v���f5>m�-��1
��F��Ch�X\���r�N~�)���,=Q
����x)�9��V�4|ek��]+eE��Z���2N��E�V��"��mX)��z@N��1�[�Z�o�����i������j��V��������K���w9�V�nJ�0��G�-5�%���FN�]>��c�tP���,�)L��n��]�?:A������T���]�j�KF��h�@�����{�����S��z-�=�����9�||S!&!n#�bK5�������?�����-^w>}��$��~��a�9������A�.O�k�G��agql����9^B�6V��i_��Yq�������U�^��F�qx�[�������]���F�����l��e����� �6�$p<*_n�XB{;��?�P#�9x��&����`|��kET���!�����A�����>��R�%T8�����ATD*!����(���D�� ~9�D���'�e�X+,����C'�GVG�GBQ"��5;j*
]']��wsY	����3Z,������U��v�0�[���+����j�[��e�EL��Rj�������\�.��c.����~�Q1�p��<�_|nx���_r�,LRx���@��qFnU�J���Ma����]j*��	���Z,~�+`9U�F�[��/��DG]4�1��*��9v�B&����8������m��~$dg0���m�%�P����0
���u�O����*�Q�fe"���)f�3������/3�}�&�f�-aj�V�aa��B���V��<�lr�\�����s�F��u"�-Z	�g��2�2���D�y�����AtX�024=eG���B��UT�2���������0���C�)"$�����A���G�qDc����*����x�U���kT1z��u[�����t?�����eW�(r�U���7��c���<uP=��6P'����h�1������y�c��WB���k����� ��#���^�Q*�F��s�'��"�T����Snp���2��,�Y�(Qh�!���ow2F_e[k^��{����G;������+lEL�h"M�H9����T���N}��S|?�]���p\	�u���3��Fw�����6Yh���-h`'��}c�J���|#��<]L"o_nt���
�B]��k["v2tb�O��R�L4��f��u�Zx��������^G3�!��H//6�M�E5�`l}K���l�����������(�����@��3T�I�"&�Sg,C|�L���sfpN�/f�����������b+
������e�e$�M2���[�I�L��6��V�d��B��3	����������D�]5�NZ!�s�R�n��IkP�;�x�q�f��W��]��3�6R�����
?�����
b���9�(
�+��+�w	l4
��+}x��>o����}e�% �+,�b��"�W�Z�����x�� �Q������ �[�%�\�%�A�E�j:c�y����NlK��I,gp��	���%�rA����.��X~%\U���L b���c&�F�t�M�����3���Ux\�R&$��*p�iG 5.��R��&���j8^L������E�y9�]��;#��H6���r�at:�;����bf��0�?���nQ~'T��:w�=4�5
�0��^[B���c��1>`l
�VP�G��x�5QbK�~m`x�k#c���$O��J��q����G�7��������t��4�o����7G��t4��UY4��c��|]�V�|l4�[��&0}J�c)���D�H�01�R]k����{�����O�%be�(�H�&�NB�8'~J�2I<��
�_%O�~4���<*l�~;���b�-�����5�Z�x��r�p'��#�&'O��q^��w%�C�����P�{T�M�b�F���z�1��P�_�>T�o�L�@��y�
�O��+� @�u�C��n��<(�n����"�A8���u��>>&��@y�����Xi745������U��[��D"�����	��j�Avx���#
�O]r���}]�E�l2��d��
�f���$��O)v����U��<+��Y���
�h��M�@fw IgW	�(o��D��3� �s>�;�D8wG����e�b$#�C���\+���%����9�0�U�����Tv�d��Kh2tu�j\��?CN&�.�xHv����AOV<��D`�g�����(��	l�^Za,F����<biHA�X�������i��&w�og_N��)s��B�;��!��z��iQ;�Q&�p��0B�d�������3��*QT�|���AB�������9t���HJ��( �=��@�8�?�$�C�M�W
�-~�������-�)��7������C��[<*!�^bMUN�Z��AS��id�������a=���X���;�v����dh���s
������k��]E���t~>;i�Z����/�_'@�C1���+?8��G7|�� ��J�V
lK��A�8@Y��,�r��4Et�LAp�H�����75�<�����d\��
��o,4	�1D����b3�k-p��78	w".�d5_�{�`��x��P(�������J���G��T�4w����i���T�t��z
���^�t�"�2�&�s%V8�S!�F�3)����'���OhD�v������Fep�t�p1u�
��8��]����:b�K2-$=[���n�5����B���'�l������P��/�3�:�4����&���b�������Im�+;o��T]O�)�Yb�JY�E0S��=�pB2&�T����no�-
�����O���"�Xl�A����*�cSV:��Ii�xQ�����������@<.�5>������Y�s��uv�;�]I	G�jb����n�jR�I��@'�p`RtJ�Y���++1r�wwr�������.��K�B�����hm/f�;��Rt���D�KB8s��,�s������.Z��%�FG����]w��2���B��7�GEDn�[���LqI�^C�cc����g���Q&s����:v�=�I��n�������)q�?�BN��X���������"����/�pd��`�N���e�Q�7d�i��A��pb�,~���l1=���9��4�
�����v����Y�7�Nd�P��kCz�
!f�BH\�
�C���Wj����*���"	n����n�I�	��TE7��I�n74c�$�O|��!�pzK��������s4��P��0~�EU
@"b�`+%@�3bc*n�jD��J�	?��e:<dj<����1����	j��]*�ltT�7�d���	}���< ��c���*�?�|�����x�X���#���(�?\>�g�`S�@���������'kH	OKJx����d�-�,����GJz�
^��!�*��T�\K��J��]�5uG�$i`��I�k�m>�%���E�c��D%K��YyL2	�����9�;�&�UXH���+���@KJ���7]�qt@Q��.fq�hM8������ o����)�|���;#�����Y��b��"<�[�������x��X`92<H)R *M������zcW�ZJ
t B��rZ�B��oe��]��BG������I������wC�k��������U4����=�v9���c�P��E[[�X����[@�l��+h./���w���G ��������;<j���,j�b�0*�[-�R��)��AF�L�YPj>-C)�d\�]	��H�
6q���r:h�<�BY��4O*N�SN1��6'S���'���E4O�W����#1�<Nh�8"��7�)�k�`gn�m-"tv����P+� 
�p,
�*�)�Jj\ �����jeJ������/�AKZ_	�1��I�=vw0lS�j��r
������
K��3}C,?X?��v��
H������w����;<�x:���rD��X���0�]\^z#a�6��hAr-��W�EC����2FH3�7?r�d=�����7H���8�d�!�d�W���b�[��o�a�L0���d����5N�xa��<j��d��8�*K�beC�A=8���4W��!��5�U�m��!��U������1���>$q��<�%���I
�`�{�"��$^Hb�9*0vE �k��q�&������S1�&���{����������U0W��7},��.`-�Z.�G��9K��#{`��\&�i,G����7g[�����X���>��@������eq8N����]���@��Xn���]����n�8>C��rQ�J��]�F�5�	bM����	�e��m���a��	��i���i �!��;�.��������BC�q�z"T���/�
 `p1������[�h�\m�����"TQra�>���~J���`m�
enLI���D	G����b
�K/,�����q�d������P���]_jR����>8��)bz����h�<�7'Jz�/�)J5��J�..����b���<F��~�&�����bW��X���Oqy�m���-^�gr��I���y�uG{�G/2FH����N�=��r_X�'�~?�������_���#_��������1�hK�����<�������vZ�1jUG�yj�V�����t�(S��jh�[Z�������^��]��k���'�v�y�>?Xg���O:�;�����{�;�`^T����V����`8��~������6&���z���~�2&����
�����e�Q��4�'!���������nIe�aY��7h�� ~������?kv�*�	�����ug.��UDy�E}�J���<���l��>
����R���>���OQ��f[[�A�������8s8|#o����>��8(~d^i����1{��y��L��@���|���.N����Zab[�-������]_\zA&�����x5�&F���6V-$8���E]I���s���M�E�9���J��z��PS;�g�P"�{��P�Y�%�����,J��+z
���>I�/L��)95	�s��%,�tK���o���'��_�����G���*8�SS	�UN"��BB���zb������R<f�u�o�����j�x�h�Q�>�]$����#R�Lh��R��T�4�.���+X���z)����,�TT��\w!%b��B�z�ru7���b}�Onl�!/��L��_yS�@��U~�&�(B-�N
)����hC`�(L���^�����r��v���(m+�o�T����re�b�.����L�
���������y�JX�"�-��ma���6�5���B���U���l@��c��7��,����5f���R��8���{e0�o2�����1(�X
���JU�#u]��j��M��[�^l����+�~���������a�qq���������jv-�����6��������Cm#i��j���D3�%.b�kJ����Lz	t�l����3>�m������V�G���t�XEqL.�(JB�(�X�`,����Q���;*�4�=$�Hw^�s�������
���=�`����J=���j����*0��
_O����wDH��$|LOa#E]�k�C�zx��;x�Kv���T?+rL�@,��f^)o|��rm��/�v�:�C-��Z���c�� �$u>��{U��X��>�k��N�#�&�\��#�h�H�K��z1#I��b���|)P�~kZ�{����~��Xd��b*S�\�I��W0n������2�9v�����O���@��	�f�f��U�=�P�Ho$��EG'������a=��v4�H1�P������ALJ���^w�A��pP�
�4���a�Qoy����g�o�9YS(C�K��D�}��j{EOC7ND!�E�u�X!�]5}��J��ZQ>d��q����y6[L�9G��f�T���Kv�!������X?_�������Y�.q��U:��d�!9��(�v�R�U�/Ke>�0�
��'��	g&@ao��?�(�1_�T��(������g�����S)���s��q��G������CB9�K��?.��%��,"R@��u@�!e��{�n����TQJ�^�MH��DXTc��
�����P������t�D�S�E~|����0�L�B������r��A*|�t�j����
�I���v�Cy�SHa�����l`2�!�8���Lh:#XA5�����E��Xa����m���'
��.�U��s7 �{���D�����c*�g����4�x'����u���z���x)��E��i��bY���P�z���O|S�w�(�?,��"f��F,��4��^����Venj2bA�*�N���=�A��WzV�Xf5f[d�����$6X����������3PE��5'od�n�t`���U���\���Z��#�p�dY�=2�9���vx:���j-f� �h�=f����F ����-��	y[��1��R�I���>H����Dp8��r�AU��s��>?"���E^E-t1���=u�����>�gU���A������C��������������m�+��Ao�WPb�qrH���0�J�?=CwN")����������P�&����w����3�=D�Wq���%�����I��=�9��]�����6�:�k����c���$�B����y!l�	%	��`wFx��Ty0fb���?v����C!8'cSl��
���}�C���M|�w��X0�EH�"vl�����^�t���|�e��=�(���^h�|�e�Kr6��j�_�^+G[Ew//]����xo���w~
o����[�]��F�P0��<v����h�r��R�������<�����������N(���,m����r�I|E0�B	8���(�.:�����1\'���@�QC�r(�����3#���6'�z����]�E���w�c�������P�|�'D�	
{=Hl
7&��E �����<�H�t1�WMt2������	���$6g�����$M��[�����)���XM\��N\�<4���%Y!*t1�.c��=�����ho@{�!���~uF,~�����2D���I�����0���"#����zL��.:l�d�M�K�<�G�G��g�5��Nj�:�@�����T:�	�c��
�O���	�<wog��e2Jz������W����R*��8B�LoVxx�}�����������_�M�g��'��3�����@���;1!Y5�9	o>V Z����"��' ^Q��;K�<%E=����bb��H��8���3_ �oB��#Y��(r�
G���3�hi-�P��R�����1��"���C%�I�ty,D�{(���OCt(^S+�rf�A*0y��9sK���A	=����~{�����po��5<�I~���_��������n�8�,��D��B8eG�3�����c>JY�������N���������`
0e��*�aD���Pc��"
��{c���,g���/�(G��!r����y��=VA\,��El��SPQ�!���CI�E}�*~j�"�����AU�L8����l(*_���-�J��r�k��,F��"����bV�	x��9������O���!���r�R<��C����B\�����:�3��4|����Sp4=N}���t����OD�*�[�WP�8���E��s��f������?�TE|�lW�BJ�sg���A8Hn�R��lb6%�IH�t]7�9���w�x��>a�)����b����S��}8��~����2A�?�M�	J'�$�B���D3|h�#~��������F���3-��L��a^��xBN�U�/�
I��w,#h�����������7x�3l(4�XO�wmR&�-�:��&�{��=�U~�2B�!�^���~�8Q �J���z*�0���CF;�o�7�M�p��o���|���{����o�N�~�9�5d@�������_����'���K��)�9�T������f�b���M8�d���B)k�jH��5>��0�
�zr^mE���@W�j���8��x,�a#�?��E}$^1���[����}�<�`���J������x�i�
����!w|�EQ��(0X}W����x�����G��_������M�\h�U�>�dB��Z$��P�A'd�-5(
b(��A��S��aP3G3�Q�����:*�\T�
='���mtZ��0[�rh�B#��I����J�	�
}>����H��T���Y��{�E�DQ��5��E�X6��	����XH/i���g�Ps��RaIL���s+��
?�I�s��i��bM9~-i�R�m�^pg"��'tm�6T{'r��;�����pr�5R�
�
�2��It%4����$
�`W��������n?�F��1!��.&EW�U#u�������hI���/zm���5�2�b�f��.�c����U.
��)��VY0�Lc��z��qy�Cdvmx����G�+��P$����y��Z�?b ������x��v���b����'Mv"�-LA�|19q	�S��"�3n��X��yH����W[���3���&O&�?x����_a�l�����>�b#����w��B\��L���.lb�����@ "]�c�����g��tiaS�)-n�3���t�#��O�����;q�Z:d�r:L�V}V��K�R"���
N�i�\�Q<�92<�)F.���B9h"'��I��z$a1�����E:0
tA�_y�B���	`,r�2���FGG�&&G�w��@0�������*y������{��X��Ix*�~j���q�J^7��
��g��u�pN�?���� cv�D������������j"�LD����?>b9JS�����8<%����u����:@ 	Ua�� MY*�<1��"A�4�}�n�?KD��xzQ�pA7V�'���y��A�NH[�+&�E��`���t1�p��D
i���x%f��6��,��;�)���1��
LZ�L�������T�C�:o)� ���������@�4�s���������8�3/`�
	4�8q	0{#�0���b���W1�g^	\�1�'2*��P��J�+�2����J+��x�����	��
�U�b�9�F
<�M�\p���@��*la���X�w7��!x��"�.�@��6R!&[�E3�$�Y2tI��H����'��+��X���\�$�3-�!�z"+6���s�Xb�(Q�
����_�<�d&��~Kq!�w@�^���d$���h�G��F�u)�'�%c8����2�=t�W��{�������a�k��`�*��=9�rK���H�U�'k�9T��$��BS7�y�X���;�8�d9�j���U��]*�� �}wt��@�K�F���*�7������[#��J0�A�v��v��j��i���,���Yc�M���f���,�yo�*�_���
h)�����N"H�`�|��7�8�����)b����s������1��B��w�p�����n��F�dC���PH�4�-�M'k��B���K&�����=c�j�:2����Vu��"Jgj�Y(�fq��G�W���#^~��'�r%9���TN%��j�<�������E"���`G���ZY�q�L�Z�Ch.��HZm��Z]")�� b|��&�$�}�90,��-�0��O19����On*g*���&�8nm�����w�b��~�pSo�@*o	B�u5�:I>D�f�T�Np�R��LDI�����N��D��H��O���v$nX��!T�:�(.�n M���/�H�YW�V��u����^l�|�'W&s���W�L��X����2��>����+?����������]rX������R��%�+�'���F�����1��?�n�)���.z��y�������z[���2�]$��36?����o%�$��Q��
W����o����[(]!aW��-��\����wi4
�G�]"C;��z!�fy��)<�w~8���W�5������"�K*�O����2(Op��F�j7j���|�Qg��(�����������8Zu�Y����T��������E�:oe
�-��,X���,g�9��lGr��IP��yCJ�J�����u��&&p��(bZI���;��
3��\�	�x�����]���b�Yqm��P��q�{�)`�TN?������E���sJ�{��C_�h���2�&N��1F�~��X���L�E�A�Y��m�M���${���#��r����q����p�E�I�=8��N�BHyz���e[�X��--C�Y�T�uE��i�}7�||�s
W���'�oN�����T�+U
�n�
�������:�� �x�R�����(>'���T�X�����)'7T*
s�
��������<�>����H���_w6���,�c�	��\@*�%o�}��Y���/g�/\oGd�#��-�/�	@�
�SI�`��!��</IN��@���Xl U� ��Ea0���������X���>\'�@c��v�@��������>$ ��T�b�����9�Sw��;<9��^�'E��T 
?g��%U�{b��xa��a������
��� �?|����]�l|Z�w9e!��)��8���d�
�5O91���1w��cSk1�;�����RX�����b��y�K����6�4�	1�����=
k��
�G�����Y��ci!����'�t
�^����	�$R���]QDT�A'���4�\B��Tl�H�7�������/�6�j�-"�EX��V9�~��{���,v��di��c�:��6;��d_�9��H2�,<0FK��]��V������-����a��-�{�R&^N���0��h�Iy��o2Y���*pViU�r������hy�p��!_��2q
��U/����+:Z&��:!���8�K��T�1*�3#��d(B_�(��������Z��+HW%��N�G��"{^�x�9������3����9�_�$�%��6��7	Y���he����s[6i�Ww�T/��WJ{�d���hX�@�W���B?:?J��e�[�r>��2���u��[g8I��z�s��z��l7���n���a�w=�i�:��.�8V�Tm���L�{	X2T������;��u�����x	O)��e`S~��}�c���	������������w��vW�
`]8�q�8�7�
�_�
����U�=!]+RH$EE�n+�$��o����VK�*@���?��*�rU��h�p���j��j��8d�5�*��QZ���GF���r3��Gg��3ph���#���?�S;��R��.�_�nd���|�2vS���}3���U�C:\Y �!�WK7�#�P*�jxOV���>��������g����������f�n[���!���{;���0r�*U���x�6�I���%8�7K�r�0�8�I�x�w��?:f*�D����"��,��4*Kl�\���r��� $Ww��P���H��bV����)a�%]��B8�c<�Y�v�$T bx��E����,��M.���L���d$�����	���%�z����	e�:L��E��	F$W�T++`��C���
�n�?��}r�I�D��:���c7���Yn��7���B�Gb�l
�����F$�&��������lo�%S��{Zb�.����[�s�S�6�NMIH�%t�kB51e�R������%4�+���6���"5$�MQ#
������J�v�}L5V���@4F4�U�j���f����8�
��{yB�:���r���Rx!j�������c�=tO�U@��]��0�X$/N2��MG*�)6����z��j	&]m<:HqP�YL"1G�X��\���O+���p�K`�y�������3�U���)������jV��C��q�Lz��;�������+��A����j�a��G2C��J�U�o���9�����(S���<��.����J�M$�#���QJ3d��r���a��"
|jK���p�6�L�������6��1�<�\F���A(������u[���q�����|�
"���7��;����w"{f�0Z\^�K���n�u*��)�l���y�d���'�(���l��@�f �l���)�l�����4ao�X�]���#�u���F���!������wHOm
�����_�G
��(���OQ�0��b�p6>|�8/���e���0MIY=���+�t�*�v9��cQ���>i�OO�Z���f���q����ZU��n��c�2-a�
���a��+��>�c���Q��@>eX�g7�-���(���n�S�(n�b1QP&��a�.\��tqG
f�\n����&T�����:�=.����aR�����Yjq�_Wng��._W���\O�,����9'�vr�s����kP|�%�!@g��8��a������I�P<A�z,(���w����/4R�D�l^��W��m+�]�4�����i�t��t=*��"���(��!j
�����R7g������P�R�/M��~��|{�����*p>����ym���tT�P-"���q~�����z7��)�� �$}�s����L��[>l#���n��9$�rf��Hf��b�&��C��9|�w���5E�b�.����v~�y����B��h������X4��;k�]R����Q%0'�5�{�5��B��c����� K�	��-�K������.�����<�:}�-����
�I��_�~�+k����V��%������H�C0JD0���{a55��Z�����q|��]H��X_N|"!�G�FH�_��K��@�`r�������F���W�!�d%Yg��3��Z�W7�GW���Ef��\>2�����*�
)�Y�TZ0�%CXNF��GF}	�
,)A��\�	�5�����Q�8Ek6�Va�6T���a"��B4�H�dz������`�^a|/��V�KcZ���*�����b��8Ym����m0rg
��2Z1���=�l��nG{�S�Kj��N������Y��?�$P�����L�L��J	��i{������F��J�I���i�%�
\K<�a��"����������p�518�.���1��Mn4&K���4�/�uk2�K��*��+6�b�8`�CL>3�S���M1$���F��8�)�j^�U7>���eW������0�t^�M7)��ML�������2��r��oW��!>RT�R��R�~������m��%z����r�TeL�Y���J���M�y'O3zh/���~����!��1Y�#�B�Q��b1�����31I\�+�;���	��OIm0�&��'����
�������o�;|�??�yQ�kxa�c��V{��#7I��z�r��c�L�f����T��jx�]���A�;�&U%9[2{#�Q1�S����s��	�8}Y����f�� ��-�f��1�d[8��wN^_(���
,�ha2�����,$�#([�`���82'���jq�l!@�z�N������)���/f
c����2xg��-�����&��
�qWx�~��.#�J��eh��C�o���
�L��c`����8��ccqbC�4|�"c�����3�z��u,��:Us-�a �K�'�K:��>�7���Ua@��q�;L�z��vg������v��i�����t�AT"��A�Ds�eZk�TP�����|mU9r�� \�C���O
�����){@	VK��������
H1�
�����X���-���k\$F�������\�\�J
�k�H�?	M�O�������q�)V�rI�K���-�� }��Jju�4�R��d`3�vL)��G�+���n?����A�S�De%!���u#�.�����:=z���J�TN�0�!��F���6|�xx�+|��H�ndzX��]S��,������W��/����5�#b0Y��~F9�B�Y9��;e-��~�rD��+�#��\��V��V�P����e�!���{+G�8�P��o��7(G�Y9�o�o}�s�#��r��j�BJ+�=����[j �hE�&���2e��8���=8���^�!$��p�����h~�X�k%a
'[qo�+6�L�&�������7z�u���-�v_lq(!���A��G������
�3d���S���7����@^dI`L�4�jn���/,��;8>��Z���K���rT��F���Z�v�"7Zv�UKCXd�<Y�L�F�/�[o��I�q�/���4^�kpP"u�{_T0�O��������"=3�O��-u�!�����b�a+�(]'�=�h)�;���m����*��	6���YQ��nwO��E[�{�����c�����3���'9���k�,�>#8�L������<��|�1l������Yk����=��2ZQ�����>���YF��L�H����}S)��5�����h}�[�����1O&���Lx��aN �
U���/[�g�����w�'� ��_L��i1�w���po��|���x�Y�`��Si����b�0l����#����`�iS*a�<�p��:�4��D(O����*���m����-�u����)o����]�5�Z������YK9/@](�B�-@|������C��~.��%���P�tEO��Y���p�*���d���WW��:p/AT���\2O����8gD�����W���RAK�-�U���(�RO��a��+#�?1^^&tU��3b���n�R5�j��y��=�"�b�~�9��<��,J�3v��������H(����=7\L��W�[f��c�e0�� 	�O�g�N����1y���Al�Y����B@�U�;���|���������<�����d?�@o8�B)��WJ\���lK^���Z�����k1�a�IaHx��%����������4�Is��t���jo���rg�B�+����2�}���%�	M��i���^d%����k����������l8��wa,����-��r��
�� "i,��	|�5�y�$��XS�C�^/TOd�?�G]����Q���z�!dZ&�Lw��(m�}������7673�f�n6��K6J�jb1����[�nZ����4��$e
u�6�j���L��oEZN��}�i���<	B�WF���|����Y4v�����6�6�����Xd�6�2^3�%pXf�F
W�[_[��;��W'��C�	������E�{����\���,�����f,d"?/�g������L��t�3�#�V^BhD8����(����B���H3���"����xd[��q��V���h���$7������
i�X
�������w��y��v�+U������5��<���~�m��7N�6���v���T|�W*5�R�����0�|� ��-h
x�s�]�=X�L�C~���d���$�	>U��$���PZ���9N�/B���;N�C�|���1W�U��p�-?�q��K��
��_��v<�j��N k9�������ExERl���$#�-.���HF���
N>�(�Cw���!���-�`�5�!u��;'�������K	V�}�0U��	��I����n�����P���`6��h�4qq��U�t�����m�\|DZ
n�����s6pp��z�`${���
�K�/^}�@T����f|����<-1Ok�yZ����_p55�,���;|����ld����1S�
�(Jd������gJ������2���������q���a����:��@�#�V&L�~�;���#����0��yv�����}/NjJ�����;��x��V�h���6��aT7���Z�6�Xqb��[��X:1����b��)Re��_�V�,^8A��A^��@���EK/7�JYg!8f���������6��P-�k==����WD��@#,o*��L����l�J�w�����- �4k��u�0`��R^ki��;a���#��
�:��?��-}�������<Sfj��j�X����%�����e��R��k��=�a�TR����k�<��s)L�������7kX{��Qd��@Z2R	Z�(�������E���X��v�,4�C*��Q8�:Ax!�&��I�����,���j�ah+���"9L���s9H���Z���e���dx'aU+�����1��g�G3ou��b���	d&�*I(�,�C��E(7X�5��t1�Y��]J�Rk��_�.Y<����a�eH�WS��5D'a�?��V������a����x�2F���2Y �����H4��+��r�U���	�	�`�_%H��r��cm~�C=���1���'�G2��8�X���V1$��iy������zC(�1#����0��L|hF�DtJ����~���H���M�
�VW|j��P�W2%.���=1�(~��r�$�tgg��J�d��z�$�0C%Eb�@Sel�����]�	���w����`k�����h2����A��������%13���Bhu�

�2&�WZ5�{#���5D����Xl�������2*���T.���S�������y�D~���&�,�2+a�t"Q�9��?�=����C���<�s���
����~����C�E��Ti��JE������6
CT=R
\|��P�T���	Q_�G��5��{��������$o�3v�qm���M�`���]�����!���_,$�|����	�����o������~�I/C�E�MwFN��REMhV-��j�*��j�^c��D6MW�so�qt
�Jj$>to�^���fd�t�]T����!'��e�z����r��8N=H�s(j7����}��/�f�B�7����g������5�@���z"���3B'"t�������E&f��J���}>�@�SB��m�uE�2r��o�i���B��7R|����x� �\&�
�^��%�I�~�f��T"`����@6����`�q�������M	d�:�_KF,�C�wD�!�M�sS�P��~�g~������x�cuO��.M��d�`L�@��W��B��������qET (�6�����[AYL�.�J
�J��P���;s1N{G��*^���-�)���*a9�!p�AG��i�)H�2�������@���v������N���v��2��Q�R(Z3�VF��_��*j��Q&a�g@SD���7��S4�y��S.��<{�4��A����� 4k��**����8���`����@�L?�a�P��N����[����8t�\�oZ����L�������b*�)DShH���m,�����
8��WB�S�
�@YT��Di
:��4&�����$�H�(-�j���sO'D��N3�[�w�_��:^D-?���Y�I���`�������13Wb�����teS�=%Jj�w����A_���.�;��F�\��JY���TxL^��f@)J���V�%��0�X��>�Y��r|��*�+3�A��h�����W�a����R�!<6�B�
�D�7h�-l���d����}!";Q�f?�!��eS.�j+
JB)2G��=cVm��	�,��l1�L�}���j���p>��0�t&��]�\��q�����E�ul�(��Dwxx�>���O�9��j���v~+�0����wwd3]��W���%�����x����(v
�4�����D3F*]m�*5�P��6�N�����#B���[�}Q&cp&�[(eD�I�X�jvY�~�:r1�Fx'�������g6���\4����eLo{����L=�2����.���W*.^y��p2� ���EW�g3^��u�?Q����3g���
@��;(�hD������71��K(K��Z����iM����I�����v��>s&��o���H����DQ���[�[LFx��PdRN�'��B���^�:F�y�?&�}���{��q���������7����?YL��c�eW�\��������E�����z��4��p,��w^�3���p�@��?h{D�������o�,�J��3�O��*���8��J����=���x>�fWC�4�������v���h*���N������?�u���S�_��;'��������c�u�ma,��	5��o:�������W���lx�G�y�>;��K����&������3<��	J{�=|�O�?v�I������>���'����Q�j�v~���i������a��B�����o���6L
?c���������Z#��}|PiG�OnFn������t`;�:j�������o�u������������������?����z�Ul~�:@��G|��0�����������}���\���pY{��w|��HE|��~����_���Z���e��{j��|�	���t�%�'7y�I����:yJ5�5�!���uY�9�Q�j7��I�������������������|�:�s%���I�N{��b�"��W�����o8����8�V�Vw��d����,����1z�3��������>%���e}g��>z�?�#�2��F�j�*����`,��Io��?��"�G����y�e�w��O6��d��c_��:��}N�F��;g��pq!��oz���<��"��fQ��<�f��a�"�#��yJP�	g��{�L,'��o�M�#�c��1���D��P>��=��������'� i�=�����I_�8?�^���f)��j��-���$��M�lm���j��Y���O�N�5@3��s��
���Z��1n��!1V��L��L���8��������;���T���MR#��
pm���Z�8~,��3�r����	*�AE���
������4�����j��
z�x�"�/���T��Rw
�y���!�)����nX��OC�5.s����-����C��~�{�R���S�n����!�������x�(cL���,TM������+Mp�7Ic������FM�&=�������#������n�%
��)l3��@�"�Y�c������'�y|�}����z�G�������o�H#�-����?���7#�����TV���I�1'��q���{���H�����j��I���w�,���lk��������]3��p�If$����"��RLW��mT��Y2�2���>O/
D�S<[}�4O�\)������i#�=��J�EL�����7o�1�G�,Q(K�����(�Bn��[��'���;t��� ��X&�������gO;o��<}�����&����"�1b"�%g��I�{��,��e��-'YF7�5�4��,�&7|-o���(�A��Jr-u�?hU�{����Jw�Yt�R��W];�P^O�Cr(�i�`���	�'���������N0�A��v~�]�G�)����p�s�8�;��	���Ir7L���.�s���9�vy`��jn����aF���<"��8�+�X4�c���8�"�n�R=c"4�:�u�,"%I���(c~G�.�k���V��j_��$B���-�����8q���0��o�
�d9ms&W���4��Hw@�����V6����`Z������A�Y�2��CQ�E�a}

�j��2}�$��������UD���~g��{}��z����!]g�KV��'H�r�vO_�>�����M�'k���|�a}��z��34���>$����O�e��{o�~����!�������J6����E_�����v�(
���
]P{��A�s(:q/#
�3#��V����+��AW�Z96)�;�K�jf~&�f�o��SL�M�
u�@����2WZM�V-VWG�7m��g$�R�

'�,��!�����H�a��-��]����?���U�M�����"�����8��%���
_�rN�w��M�X��W�:g��?����t�o��AO�
���[��
���W���D���j�-IF',�\]h����A�d�,b��b��8�s%^�<����9���eN"�E����������B��xg����	��j�%Z�W����o�����
V|W�&)}=���X�X,��(6o���5�Q��?��b|�y�h,����	�co&w�Z���v�������R�74��r8CL�!!	G��j������
�y�6&���3H	j��R�}�[-�x�^&=d��!�3<�:��3����������OV�\X=&gL�*
�
���1��f�R�� ����Z���h�A�o6���a���Q���M��=\�d����L"M��L��7��� ��Zw�k��h0�����7�[�7�����z�e7����y@���T�M��A����l
�T����9��
��w��*��v����?�S�R)������?u�������e���<���V]|���g>�:/G�^+����YA��
n��u�n�`,���M�E�]Nn0e?��'W2����3�j�lU���j�;��{�CjH
����k8�F)1va��}e����+���2,~�lw�������/���gH���=����H4A�^���Z�[���O����$#"-�yS���w�m��YO��hOpK����DLM�.����T��V%�Sso�
�<�k���^]Jd�����&t��7�9���V�.���Ra� rX�VdS����"���	��D�)�����f&s�u�al�FKo�g�'p��bb���	�����rp����u{���
���|s�%��[%�������������bn�����\�@@�cH���TVfQx�0���9�5x#z����u�9��_�@>�d�����<��q�b�x<�?s���)z��?,'$�7�C�(���B����Y�-��4���.���/.9�'��/�"���z�+��rW���&B�w�n����t����uB_6���c_fXM���-��F��$�Z�uf��,����A��
N������~YT�O
ch����O�#��[�����0*U���k]�����J��sj`�M��(�$3p�P�R	�"�\�9h���"�Cx
[�m�zcWay}����k�7	W��t�2��vxW~��D<Z>��?& �9������!F~��a������m�T���"�C>�D�|9��A��pnWwM��^Q�����!P��nl�4c����M�����7e�H$g/��T�~�����`NY�5����/���x�9��h������4�,�]��ECy[{�M-Z3m��7ub�%��&�Z���������tKU����LL�\,�`���&���R����g��""��a�k^��B����
d���o-/�L���&$Q
4�@��1	�z��RF�����e�� ����P�����������S-��Ra($� E����F����|����UDh���I�����QZ��:7r""�E���Rc�z�����N|w�(�*���'���{�#��I��o3q�9-�5.�\Da���zX  &@g[����Ka,������DOU�(v��`R���	�z���C���'c�s��~&|F����0s/��D�@��'�+b�i����Z�uV�PFx�-)/��b�mw��,"�����k�K�W��%k
�\O$���Y=�+���D�"x���R����/��1�$�1�L�t���FhVxA|�r���G<L�l�KF������~���~��L]
�s�|H]7�W�V�+���B�$s$�A����0�G�!��s�.J���������:_�R�9!0`BN�	<�����Ht8`����+��+U��G�Y��q��47}L�X�]-56\|�bbD8�s��o����K�(������p���v���\�w�V�4��1e]��<f�c���A�B�{W-�!��k�����(w��0�p-U�j�����3O�'�E���5A��?|r�$'�snQ#/�1=`C0pQg����QA��'X������v�������"2�����J����l#��N��Oj�v���pp|d���|�A�@�t�	�X�!q>F5���+�?��K�lR(B rmRWG�G�G��W���88�����H0����J,��31�:Hw����"�v�G��L��eK��kJ3�%<�83�Z�}�����
��ws�|02�=���������iw�y���1�`.���p"M	@���E�h$�,s��!����C9�U�Z���
A�l@e^��A
�D��q����AkIf?������5eA��b;w���P{�f���Q)4�R���3��"�"f�$�C5)g�
?,���	e���o	N�>[d� `��� PRJo��dqT8���a��!���&W��:��y]��E�l������#�2Z�Jz�B~�=z�)�`�iZbI(J�%�(.��}7������dz���{���:r���z��6��xQ�����.'�)u-����i�^9�d��]}q��b?�A�z�R�W�x�G>���3����#�����%�������`:@�(��]��(J����b:�d\c����MP�*R0���O��S��c��NG]�>YD))��$�=P��^�V����q9p�l�r<�<`K�?�zv&/�8��H�yZ\$\��$C�|08����f���+���q���d�e4����nh���o�'��-2���
vls�?�l4Y�]k���?{���z;�J���<��/�!:!�3�e��M7��
_��]an����s�'�b�b��3�����&����& IG���!��a&�H&��Zrb>�b�k,���D���O!�	c��u�'��-ni�w�x
<��c�M��m%�x���W�+!�gp�����Z�.IPg6�(��XM���:|�t>,��U4��S�A�]D���u	�,�g�����m�e��s��L������6i��fQ���r�.����P=����QmP��;=�%�IZ\�H�v��Q5����j���<��*K9�Rl������������+1�RnT!:��E8(v�+cn?�B�!RuJ���s=qI�����`/���8�Ap/~k
a�L��y`���)E7'�~kO�T��8x�l��>�t���si���f7R��o�����T�_r2����D�����u�*��s�>���U(H|4��W\��j)��T����uPA��EZ	`�`�=�	���_~7�m��������K���Vw��7���� �}�\/�P��h���`�!���1��5�g��#��#�8D�w#-�%�	���h�WH5.���-cv�r�t����(I����s)��,MYfq.��dWD*�����l�;f"Q�[v�&U�B���XH7�Y��.��~rY��
U�7���R������>��Wi���J�L��uL��Du��b��aYwn��&>k�X!�6��"@������I|Pw�J�L\QP�#�����������B%�!7j
Q=�b,v�������9�8���-)�X�%��~(U��):5lo(��GC��I��!�,dy���J�2�m�����h�H�E��E���7]On����YO�x�E5n�z
�y�z1�$����:���K"^�<� ��kJ}�&�9W�7�6�3�����i2����Y��l��d�6Q���������Mt�hJ/��4X��K�l$�s@��C�~tX$���&r�"�z{"g�a@�&3"��\*a1�r5�R��rY��U
�H�I�ve��bV���~!n�%���vF�Z&y�s6�fS�#�l4U��l��2_U����A>���28& ��O�����'��!~�ZT�>�
o{z��Q���q��F=r��m�+��To���B]����.:��(�h�IOh�FY�80qo`�'�������N�uB0��� �l�}r���)]2���3���/0u�Q�a(dh������SW�C4W�4T���C�h�������+������P�R7���s����_�'��Hq�y�,��?�8�F
y���������_oB��g?�f�l7+9��d�P�n���
r��!���F!Y��?D��6'�����_�������%�&'a���=9�9�e�H��s"=4)��w��8�E7�,`"��/2�]��,���S������,@��=��f��xK�Ds2zQ]���S���YDY�/������4�y�E>�+��Y���@�eU 2�SY�/7y;�$'v(/����G�-��/p������i���du�FZ_n��rY����C�K�:�-=]�:Rz�Y8�f�+9�]���L��o�>8~_�Z����.�pR/m�����U��������u�b~�������!���XX��#e�'��N�c[;7;��Fk��B�����w����
>Z��p�P��;���/�����wl�Mg!9n=h��`�����{�8���d�����X[)��R)%�)�T���r4�����m[�
3�>%	V�Pg>�}�.�3(���n���K�6[�{b�rqJA^�e��������8���Arx�����b[��fTU����r#~�#�#��3h�>,����������� �rg���ZfE�P{����A��:*�*i|��z��^A>����j�U��"Y,���h2�%���T��CL�X���)�I�[[p�%�h����FN��Z�nI_9��o���#b�ld
����(��<d����U��-����;o]+����p8uT��$���gW�����5*F��N�-��C`m#�B�w ��Z�@��~l)�
��`�x*�(XQZFFb��o_���
B�KT�s�>O��c���a����(WP�-�6�h��W��C���1���'���<�
��V�w���rk~}�`�qt@���v�]�J�CV��r�V,����h�
�$��Myj����'�@!~lkA�0�o�u����_�3-���]j���}}g|��\ :L��
��F��1����\W��b�62�\��7`K^���2�+l_��������"���-�
%ae�6���l��:����l�6[�$�'����O����@"%�*BK�Rl��!^8s�����5vor��Y���g�U\��y���Q^Nj���,"���H��4Xo��r���{�lT�b)i�Z���?����t[�|il��v.�_��w��1<��8����P�!#0,#��j�j�/����e#�J�6�B�+6C�Z�A�Z-�S���g��I8/������@� ���l 	�Bz�f��������
o����6u��
<>�'�-q��MR�_��2
�>?;�u���EA��;B����~��&x��l��Y�?x���1�Q�w���0)��m��h�	��F�J|6�n��,e�6�f��������:��_w����2�� rzv����0S�/:�l���c�(��ON�<�2A��CX��M���kr9�z�s����~r_	��gh���2+����E`
�UD�������I��4�����5[p���y:���c��9L���49,q�P�D���V�~?0��8��0�_^<v��Bn��4�|4�`�`4
[D��z�>Q(<��%��1��-��
0h�&C`����4�5�&\]��LhV�������(X�v�������_��[�C`W�TU>{����0(e�{�un 
8����S�h������M��*	��N�d��x��*���y7gO$�bqi[$������.���z���@���"O����[9�E�����<G��8����p�����a�����m2#���)��Y��r�E^11�m�o[
3����1��#��h(�X�E4�G��1Y���2Y�G���V�6��(��"�=���1���;����1�G�>�
�GN$�H��}�)�����rR
x��~9[�Tr!���-��%
�qI�Dd�5�eL����%9�w��M�_�����u�%������|s'�s�,�8a=���Tb3�������{��m4�	S�t*���m1��z�����b�O���������S%&�7��&�����@�8�7$�6i'9�����Hu��n�E��5$��9�Wz4	�F��V|O�P������
���0w\���b�3��puNpw��
��aS����	��a�B5�.~�U[KJ����/�[�,����3��bX}d`b�FMj	//��l�[��'1�����9Zb��-����u���0�����Ci���|d�]@�=��p��<��?45�'���7!O��!����0�fF�����Ffan�J)��}���;��iz�������R-���*�+����7���dOI�K�Z#5-6jv����������d�;�^o�/~���Y�������?'�W��u�OS��k��g�H��Jz[Y�R������7�
;���"�{W��r�n����vB�������y�������y�C�@v���K��k[��e�N&���8�T�Z�0����}g�p�@��/
"�e4Qc;	T^]/��Z�]��'�Xl4%�i���.��[nqn��&�o$�
�C�*H�=���RE��ra�#G��{MZ����
L�����(o%:�S�F�!5��,9��h��-�����!����<nv�h�h��cc�
*w���S�����r�4��Y���x�!G��R?K�!�)��u|)����<��J�+�jP���	XF�u���������k��AT6�-��W�K������4����x��3(� �����,0��$������M�bj��>>�������
m/Y:=$.��O�a6�$�DMQ�)b\�v����B,E�J)5�?I�����V�)����B�D+�=rR~�m[���~q�~�M�����U���L�������6�����N���,���]CM��G?�����Z��I�������9���H;��bX�^�]bS�gi�N����g������R{��YF�Uw�If��^�L�~���f��������7:VO�{������'m���I+E�vQ*��Q�I��d	6�G6�X'�'��u���e�AN��}���U1�*h 1qgt��V�"��KvA�<���5��n1���B���P1&������X������%@�F�~?��������#cp��!�\6��	�+�E$�;h�5gvg��A`F��zC�g	y#��F���/#tK�L�G���PI�m����m�����#��'O��d/%��`!���p������B��������,ZD��^x|H��������M�y��3E�D
��i���	0;O�)q'Rdq@�����
_�6�r����`����j+�"�:	� �/b����Q�-
V�RvAA����C�����5.��!���6N����m\�����jB[����=-�����<*����8Y��PD���77*��Z5���Q^���m+��`gq�;�����qH�*q��`��i4�~�bD@4 ����BF���s�c��}��-�B������-f�z&T�U�R�1�r���+��BkW���6g�����0�{�^%^�J��U��jLb�hH��\�������~��e0-����k�1�f��1���6�,�N���DJ�U
((�Ly���#L0iA�I�����gp~v��+RG���~�E&p����<��rFy��j��� ��r�\�R����b:�l��A�����i�`����F��z����1{=j�U�<���"%��
��_*io���az������>Pr�7����p�H�7�d���=�_{W�����j5YI�W�3� Unu�ra�s���/`/����fg����������6X��=��K��9����]+^)��b�}"�����d��u���Y:���T�[.L�r%�W/7��(��
{3�Ml�������h���W�q�s!����E��w�d��Zb=O\�t_�p3��-�������g?��; AlMG���������u0?:��������"��B�w5���w|���P+��Y��Y]Z5q=&G����+�����t�JE~�'f)�G�FD���7����K�
)������V���Qp���x�O�DJ�fV�WZ�"@B{�
	>5�)%��M��r�"1�A���0�(��������@�"��	>W3_sS��
)w����`���E���@I��B�`&Q$a�/zV+�9=a<�QC4S�}�C�Z^����z���]�������RB���J� �09��$V���@�r-x��B��I�o�
�
�j����`����R1�m];�p��_)@��@�����h�mEt�e��VDV9��Q�4���&���F�RM����?�����Bp�)Z�������98IP��	��8s��#�*��h�n�TL�r����DI�X�7O���^�\�~�����7������W����EL�U,?`��T�h����Q�'�Q|���c�[��;��-ycgm'/�9����[b��E�%K���7����pl���,@�$;�:k��`3�C����a��Y�<�PB�5�IP�M�{k���X,
3$���l�fv{����o����l�f�Q�����NLzg�#����S�0�b+c���,�N���9]���s�$E�|��gc�?�~(��v�\)��������KE�����h\�^������D�
J��q�>h\#O�&����g�����+T������Q�CU�:-E�J��'���Nft*k�J��#:�{6�X5.���R��J���@�����F$D��/hdD�&��V�-A�:�Um<@�\d���/U����U�~���h�Ak����2�����%��m�d���g�L��KU'	X���^@��%
?�������&�9�2]��_^�3�
������U+�:2�M�Z�.{MO:��T��W�
Q%r���Aw�LyahP�H]�MYze���V����ln/�\O�6�[-������bb��m���p�Y��~�M�;�����C�Z
d��fwD�k�8[���n��%|��n�_�S��7���y�(��0�`�	��!���t�|��%��:��Rjt��|:fk����"c����;D�*��T�\��1c���Z�$q�)$����I�o���{��	k�J������b	-��<2i��*�|��|_��cM�RN�l���f�6���0trHA��g��J�"$<~�H�;'�3t��L���A�jE���~��K�R��	-�@�|d@V�"�1eG�$���`���/'(���A"Q>��c�B����so��0�)�@K��Co���ka4�:g<d>J���j�Z�T�y�#�=��e6`!Ty��
!TKcB�V�!�R�P-�"���!��Q����J���1�D��*�����I��4YJ`c�"�M�T�������G�DNJ������`���-�Sy���vR��"��!6�h:�++�������;W��q�HzeS�y�(M�-�+��]*W��T��|�C�T\����l�Uev@�J�i�����;�8G�/ 3��?���{�S��	+�wst��d�Y���J����������V������d���bI�}����M�3�I��>�k���C�@�NOQF�����hlD�R��T�>��U*�05m�^����P��pX��Fi�;�%����St�zDx���e>':>�vr}��	��~:|g�v)T��:�|��;�������S�I�d��@�)�:)cm��!�n�vxH�����q7����s��&O\�p���	����i1+��X5R^���K����8���8��8��X3���2������2���O���DU������N�6�l�E�m��D\��r��������r�����h%e�(>Q�����7�P�$�����I�;�]6��%"�-�����D�H�T�q������?E��-��
�� �.&w
.bm�.I���t�:L�P�������m��a��|b"z����R��tf����b�3#WXo@r5`_��
�>^E��<(�B�"N�(�,��4��1��
A����;j�����n2��y��t v��!��7��Sr�F��)h3���pS��+�-	�����&�+�m��;I�/v`��/��DGs&x�el���,�aq���w�r�4�/��.�(��W�<d��-�|x��,�VK5�z���>�$EK��o�x�X��?gE8���c�M<�=�CitN�%��J����J6�/�r>�O��}>����S��|_P����Z{����E���k��7C���G�:������mm���\]����5��1v�����yK-��V����cR�"��3�J@����"����
-e�ry�Ze�����=�LL��Pg��s�*�]��O�@�����=-��J���e�QIk�������Y���:B�&���%��|Q���������\
�����������]�[�!g|F/�#���I��_u��m�9�.�5��$�*����!6�(�Z��_2����y�X���KT�?B�+<�����e'�^F$��_��4�� .����K��@C����KL��[W��
@���*�����IL�$s���u�
,��l'���+�{��/�mS{Z�? �=�!W�!R��]�OL���v�a���9��M�V����6p����)�u�Mu�\���%K����c3�����K�e�X1TL�0���5�_�;A��P����.#.���/T@:*�����\QK`�Ay�}�X��o���������C�������V�T��e`�x�5E(�M�(�++��������$"OH x"EQ+���o���9�rfT2���(�d�~�b�R���y��v��l����_�|��%�:M�m17]��D�f!N$0��K�R��_�YSx���0��vu�#��`�V���(���ZO������-�{�2��r�5X�j��)�{�{�{�[�n������f�kr��l�!|p#F�1'�?+|/40C#��ZW. ����_&�%���	F6X_50��8���q��1������7��~n>b��x�_���{�d��QE�i�VN���L�����?� U�p1�
�����w_���iIe_eq���r��A�GQ]�]��T�/5�2E�����_��_�?�5 ��/����fa��7���L<��=%6�l�r*m`.����%#L@��������u��x�8��F�I<h�Z	_�G���;g�Z�d�.�E%��a��dBjr2��������AnB;���x^�~|:������BT���X�O�4��s&o�sk�qm	�x��9���FlK!�W��Pd0v��(���([IS�-"�Z��b2&��

�}���^�b	��S��6�M
eC������I�{����&�����4��bq��R{\�`��$�7
Bu���%���x��s���8G��C���
a���y\�T)5��?VJ�2�"r�f��o�����[b�Zc�X��"��.z��|F�N��������)��Ha������@5U�v��&���T92����]j���.�m���F���I���K� �����Z�} ��
��j�����D1���X��/����X���1��av;��u���G�R������x�R	Tj����U-%%��?_�sye2�H/&kR�b7�(~+���V��q���Gjh�����F����B��n��y�4��A��T���&/�{�"��k5����i��������������A��k�%�+7���
�<��de��� Z�u�[�6B�Z�5B'_����4��A�s��J&���F�b@�V*���k8,�b�}"��g��D�A��5H����;���wG�W�����}x�� ��A�zE�k3�KE�$�l���v.��xy�}������g�����{��2����	�ILJ���AA�B�@9����p�(�+K#�Cc��A����|��M�/W�v��HB_��������
�����kK�D��D��Kv!D��]��I�UC:<�0��o3�����O�������z~,���8��#dT��4��R4-`�V�[[q���������������\���a�(g�l
z��'�J�(�X���8D�����m(�������d� G�j
}��E��h8��ya���fc���Z�8��wg�������7�m���$e���r�`'U������[�!G��l��y�(X����"���7f��}���Oh ra<eo��1�]�i���v(v2�����Z"�����X��A�?�]L�X��	0�����gs>����<q�����rS�`�N���0-�,��d���k�l_�W�P�K���rsc
�?���4}��iw�y.:�`�ZSNC,�UC�"�-_�(�=��l7�VSD�G/���0H��&D�"H�O3}dc��3� R���I���Rv?�����������'#�&�Jr��1w=�����7H�Ke�g�.4�/�;������r�T��h NN�b�\BP����p���pRa���C�R-Q$%�dfe�������3�6	��,I��H��5A��~�%9"����N�B��O����0?�dV�o����.�r���A�I�����M����j�[�e���%����<��R����j��d�Z�?B�*Y��?�?�����3��h�7�E�?�Xw!����Lg@Bg�e�F,F{�-MVo�������kYFG��1*�aQ�pR�l������N�}e���KVTj5[E�r����(_����t��.F#��
b7r�IX���w��o�7np��������\��#�J��`lM�������n.����.�b�x<�!b[mG7ag����k"�$�qv�����
�+(��(����?>} N?!(��
$�Zn���"�<OD���9��59;7;��hg�|�%��������7���y�joMSn�Kv�"���
�pfG��.1kT�:2��5}_#�Q�F��f��==B�mc�6���l[��P��V��>�_(��N��v���G7�G���K�f����YRGm��n��8%�3N��~q�?;���2_���)���Cl[��P�\�j����x�]d�������_�����{�!p��Z-o�{�?y�3��%e��IP�H���a_�kBV�a3���L�b�,Z)�If�����6��ad��0�U8M�����Kmm�R��F�����������	�3�
	E,1U���swD�st�.� �CQ��5�w.�6����_xf@�~H2�R�.U��`��YR�����'�]�o�����i��q�N�<����]X��N��v�G��vD+���3�%�vs�D��?-��|'rLw��AN��%��vr����:�����aK7��MM�z@��g������4/T�P���P5l���_�E.yX��Q�
 ��(b����+|��T:��|���L��.vD�����_]��p��A�(++F�a_T��}�y�.�A��<_cq��Q�g���DJ�z%����X5x�g���f���v��72[��Ii����R��(X������}}�q?p	X�3�6�k�C��"���~��t�#?p�����z)�����(�Fx,��a���b�6Z\
3X�Y�`M���t�h��A�����cL�(&��3j����-q���,�\���>�_�70�����[����E���g���B���#8R"��V{<��F]�m�mK����&
R�dG���]����h������_�
��E������;�Z@�b��cL�d%��6�K6��F�0"I�T�K�0	��/�IH���Gsg�Hk�����}��nM~����U=t'������^F7��a�*���x�%�U`��*�<G� T4���2�g��`#Hx���jUD�j���,��<c5�x���2���Lu�W���s
�R�;��,�������!F����+K���~N��1>9=#���-Nb��&�\G�L-��T��wEH�%�����PyM]g�����G(���B����QQ�M*���������a�RPZ#�e���1���.�TY�8
0�:k��a�=&����N�;�^gp�;���~�E2Y�.6_?��p�w>x{z�V>�����D��h��Gc� �����+�����8:�>�������e���b+#Sd����l��D�I<T��0�;��q�e��4�F	�]�(Q� �>Uq9�)jEBPR�WE���Je}9�(��,Q/nh	������P��P�P���p_?'/�R��B��+pT�W+K���h"_o=@P���L�������:�g�T�e	=%�4�0�fE����Yh�����q��-���{s8��,�����X�����<t���&�\lr�S!��5�������W�x���	46���f�.�Z���\�<�2�|E��XhPa��,>�F^Sh�������&4��I�����@{����}_B���*�a������!�j�A������'4�Y&4�1�k*�u������=!4�-�P�/4��/4��
�!��B�^~���'��=+���C��+C���g��T�������RhY>�RK2��,��!Z���c�N(���Mx&������=��<���\��^�	��Z��&,Y���
=����Y�$��16�0Q.Uj0�jY���Q���6O�z�q���=_|��r(�����E.t����
%gO�?��������M���z	�So�oi>>���@+�d9���-����q�*��]��bW��#��!�g!��!��B���h����f���D�jK�[�x��vU�
H������+
t���	�����/)F���	w��H�������{R�1�X���!l\��J��JJ��'=�p`0�S����5y}���G=����^h���-��2���Q
W���C�������|Y4��
<��Wj
_���h��IY<������U`o���L��DG �vB��!���[?s�jv���]�%!�z�"0�z�COX/��1	�#,	���1}���](Z�
_�TR�X�;��i���B��N�CT����~k������R1����u��:"/���4������F�[��X}S�N�V�:6m������0��p��ydM��9���:��4��35��Y�1�03�b:������W~�g����M �.
���1B|�5_p1�G�]���?C�%L,
��8���p���+uX�j%ih�BO��AS�G���H��M�{������[�% |��@V������h������B<��*[li��������kX���3����H�51�����{�B�^<>?|�3�6z4|�6S�Y�����(�4J�[y��5���-J�$�P^Y������$�����)kN3
f��K/S�"��io�kw27{��7f������	��C��-,f�pz�_}�q��Z�5��p���|��V������k�w����`����N.���P��<��&&����������`s�$���M�!X��I����?r����@7��L]�u/��in{�3����r[c��M������	�N�k4��G#D�b�J��n�F9~��&,�Vjv�Z3R`2K��E�1�?-���J�v�'I��g���_��M����K����p	�l�����N��b�R*;�69���������}�������W�pE1��*��Q-��H��]�S���e��2}��1"_�hW����O����C�}����(���1M��'F�<� O�Q����������'tQu��1�T�.�	���&�>I�7;��O6�����M����|�F� �������R�m�.�q�{i0�-���{Y03����v���,��L���B��5�w���{�{[�<��R���#�$������P�R'��Eh����Q�o[s?���Q�Q+�gpE�W ���ad���8�e�����������H3>��g�u�{~�m�d�=�\��}�����c,�$OY����8!_gk��/��kd]�-[��uz�A��z��C=��0V��t _���}K�][�<n������-XrGZoC?�v��p>?>}=<��4z��%)W*UAk��������U�fMx��09�$���C%[��7]��l����M�t��Y�����t����K���:������vx��u����t
o��q���;��]�
����Sz���sx�
u~>;i���W���w����6������������C%O��#�{�=�}���;��_���z�3*>�a���k���H$��'��������=?����lc<�~���Y��������X'����m���?����?��A'��pn#x:���Jh���s;?;Ol����O'�����+!�":�[����M=����R������6|�R�[�lxC�X�L$�}S����~���y-�|0kc|z�r�c���0�Km��:��x|�y��;��!�`J	X ��eRB���S�}Y���
��`���2�3�>�^w����'�.&�b��
����]�M��38���;����<Q������L��w����(�}��L���>?����)����m�A��K�z�n����W���?�%5m�R��q���mi��D�T��c<��|�4e��c����gy�
B�p�L�9y!cM��y��T��c�0���E������������\\�h�Z\����5#���y�����_
��^Vj���
��6���O������@ ���*0
����%��MQ�\��-�����*�r��q)VQf�N�q)��r��n�R�{�TyN�AnE,P��{Lui�@n��}D9�g
�,����t�%,E���K!-�����XK�<�6�b���"����]j<���z8<�����i#�."�{>�&+�"[[lD�r��6�S��m�������bN����tJ�`�b�������b������Jnmx����G��� �
�`�2����?���[����MCR�zn�����5Z5��,���Oy�_G��GA#�j��D������}��6�5	Y��k���t,�ec�t+eZ!\��Q�U�,�s���@��y�ZL}a��A�-���`���q�8-$u��d��f�kU���N�7�a>�1o&�]�� \61�c���W���K1�f�h�)������i����|�I��%N�@[[��E�L�<:�� �@����S�ex-�}#�QZ<��>t�8d�0������0����y$���/��,�����X���n�O�A��S"������F�f����M����]�X)��K�����L?�1��r�������������Q�9=��;R��Wj��"NX�WSm��X+���>�\B��f
�'�\� ��v�����E���_*�j�A����(R!�\�U�/�+h�Z)�>z��k�u��]���
��x:��e�-�rm`��6-����P�
���A��.�t��*�����j��Sn�
���1J�+�Y.����6�y�^3�df�C4<�R��{b[������&7Ak��������<E�����zk��x�sa� 3��"��	�z��v�+��nV�`�������F�I�.d5�I�1W��fu�]Clu�Ax��RCd��Ar���1�?xar�aD��&���aY�VR#)�$;�+��aU)�^���:v�����O�F�o����C$�f
$�f��\"�I���|�,�d���i���VR~9��Y�[�"3S���%��{�W3�����|��,e5+Yf@X%c��#�m:�'l�6��& ����O�D���}��j��z"S}SH����F�,g@������E��D|a���}��r\-�7P��x���`�,��P�h������P�s�>�m�f���:o4��Xmc�� �*���#��;�Y*�tS~����\���YJj����l�5�~�h�b�V���[����%w�X/��[f��������[��
�'9��]�j���L���	�+�z��_�4%W"�>�~�^���?-�����c�O����V��}�<�UD���*pl�W}R~�v������v(l������&�����wU)q�����R��E;�\�8�0�����X~��IXKUTR�tr��
B�&(�4���t�����fk�%s`����a���f�,Hh�o0���}f`RH�L�w�z`�\g��es�X�V]�I.�R���x��G
X�d�h�j��C!��c�w���o�)��������/�b}d�d������y�}����?L��
��H��g�������%�{|�{f���0�������a���u�%GQ3�{���i��R~{���W&O����B|�\��+���Y�j���eF�r�rO��u`C�VS����=�E�J�Q���F����Uh���;�E���Se�Ib��W�������������;4x5�u�[�o��[�Qm�*�g�VYG(�,���1>����
�w`�;��N���G���)F��%!��o����Q�'���_wrb�1�������-+=|?�m�mw����UV��{���Gq������V%/���c����2o����0���&*�� ���VL�&9��4�����4��4����=��w��\��PQ��m�bZ����ab�E@]o6�,��3X�������x�9L��(�
��
f�+��vC�9�1�1�E?a���Y��8.���ag"a$:��S=a
��BO���5���
�HU�Y�\
/J�t=sod���NjP�B>�@z.�0.��V��R�b�d���N��W������;eB�?���������GC��������v��\������1`4^�Z���*!E�C��Q�s���!]�Y{�rG����m����A��rEc*1Z�d�3tg�Pa�����8���v��J�^-&��ZZ�����gkg��3X�+�� a�$�����@��^�1�{�YD�M�������B7�0����UiV�V�t���s�=����i�~�C�R�*-����IY�u���F�:5npv��J�P*��,��}k�n�����*��R��.����l1��L]�Krl���T-6#3N�.&#��co����������Oj/1)Y�R5�5	���t���[�<&���[���-�D�\+" ����{#�m�6n�I��hY��!�m?��'N&��+89��P����2�Bb��eA�Dc�v��gc7����1���O�n�����C���+����c[���'��;���2�e�y'U��z�����������p<���9~	���sG�w����&�:w��sG��%���)H !����������@������e�r��w�	`��U������?9_������7�������t���4����)���8w�4�6qf�5s��9	s����-�D��)^~�7�5:��K�����
�5]��8!C]�>Q��#����>���@Wn�!�[��B�����F��B�������"�
~�;���|��C����A\�k��g�8��w���#�0��������2������pb����������)�a�1_�T��z��N�|��
��>9��9�md+`�
>.<I.�.g��[���bY9���Jv42>c�D������H��g =����6�b�����	�8�K�"qj��/�u������
�E��B��Z�e��EJr'��	=������u2=�
�p�&1J�>G��8B��h:^!��������,eX;�t�����Q4i���,|C�/���!=�lhG)���z�����	�'��m�v�'v��N����5*����c�>��q��Q�O��J:���p/$�w�iv�t.�(!I9E>'��W�_��%�_B5KM�\�('FY���5u��G��519�]��>���Gm)D�w@}��,p���\����{q��F�J���]��p���������W'������gCs�o���"��x���6�D�S�p1L��fH������Q.
����\�pP�D���qF�e-u���U���������i�����fQ���2.�����J��#�<���I:�6.V��i�*h�.��6�V��bj������T*N�6�'0��)I&R��a��LE;7��N{�U�/��E���J���-������:k��k��t��.t��w���I����*��D!#�-U�F�����#A��,
���R�V:�&la���!|�EJ$<P�C���,�
*��G^}�(A�Y
��ZJG(
�
_����=J����B@[�m�;��������k�LzS5b�ex���q�I�����X���(��t�)�I���LEK���.�K	��HF��c����]?��ek�J����MX&N���������#�|��������<��j�.Wke#j(!����Z�k.m��xx@f�pP���Z&e�AISWY�:���� �5��xgLM�]����r����v��5^ o��hgX�����^����!'<~H��d�6����z�gP�����b Wh�	%eDUO�G9��4�m�QM\���O0�$c��g)�i`�0qu�gpXN=q[@���E�
�}f�|�p���a0�u���R��x���Q��(���E2��(@���f�u����/�<��@!(
���
8t�q.�wW���X����7c�������$H�@E8a�h[��a��,
�}�D`%oQ��IHI���B#VY.9!mm��<���ad[q�J����2���ZEsQ~9�
.�K2����J��bi:E�_������2J�L��D�}�*����(������k%@�Z�bFi�J��HP���%�T��?�@<�u�4un ��T�d�����&L�-N�A�<0��5�o)���0���.-S��a���@���C6?��v��U;d�&���*rE��n4:�l��+�t���1!}�h7�4\��H���(,�'�i����02o0���TB'����r�W��1�}�4������������������9f�[e��0�[���mU�V,lwg��h�����W��o���}'
��3���?]����1!�T'���3�.��
�A�7���3��/���/���R���S�0�j�}��goo�j����Fb����g���w~���;*lO����mQ�3�m��br����#YP���V��Y�������3s�
Sg>���.�U�F�Tep����^���2�z
�����.U�����U��-"�t|
���f����%}���R,���a}��+4��~�9�>u+�
��2�.��B8�p;�3�������Z���I����l�����r���Br���gQ%�D��4���������]�o\S���mj�kJ~�����/�����:�:��H���J{kk+N�Z!��V\BPH��t��N��(���1�]�>��tQ^}4���}��
��c��'�3�9���L�j]BYkz�c	�������E�b��s��!�D�=���H�H)���s����b�M�k��2EL(��nuP�wEN�����3��
�;e��a���/���z#u���xr���P@�������6w�kS���j����G^�+w�=�x����]�:x`p��o��������L@�!������,YG~k��+v3GO+����P4��������[����T���PA%�Yx�]Zp���.��e9�B��&X�DHT��m>�o�RwF�9�����6?��]�&�TE���Sh��T�tBZ]�z�f]Tfc��M(������3NrM�:�
���V���TeT��J"���.|*�����7��+�q�47����>[|%k���� �ELXRj��v�������������y
'dmM��C�*'m{�����
�JC����^w�A��pP��b���z�i����[a�mI�~���v����	sY�ZM��q�+��?�&{��K���O������eS��k�J�����d��\a�,�4��~���$k(Z���qo���<�W���[��]j���2��0�o���?���;u��Z2��n�(S�^[�U!YD~DM�*������y��m�	��]l8�_�
�^o���:��9���VOl���5\�������{�?���w
!B4B~�����f��u�V*IY
�7��v1{;	�0�����4�K3����3�-����$�����1����f��1�Ia>80�C�+������V��V���%BL�S��j!$��`4�C��i���%��=�Go�%��V5�3��Wp$;mw�ce<�/"��$LV���p�Xl���4A�u�x�����]6;$]zf���
��6��������;�
��|O�V������{�X�w0�^��/�zo[�4�f�)�8RO��u|�����?�HS���G�P���w; ����,T��
<��5��*4��<�7<(��3���k�:0t����%c�e�����v���O��x�!D�	��&pb�r5�FE7h� TL��������!(���K�����X��T�{*
��3����*M����;�-���R�[{P���V
*H�._�����&��83�������.�`
���/�k����'-
0eH������ix��}A��E X�y�Z0�S�� ���@�����}��
&�
�kY}�
y	3XeXb&��\-�N4�y����;M>�E*
�8��QL1��Sl�X�s����t��vB:�^��������C��`sC���c���&�S4��/�c�������H�M�����
�y����aOK�N��^[�F��$y��[.d�g�K�>Z�
<"W ?|�e���o�FoKT�Y������yeb�1����W��<��#f��V�&�����Xr�^\�+�0iq�*P� �F�3�J�z����Sw
g3'���A���{�:T��7Yb>_\L���A_��CI������]zn�g4�����j��?��dxs�k��v��)�Y����0vHBf�-��j����gr�ZT�7��JMV�: �������"�v��c{���0�p����v��%�t��^k"E���eYwUV���eQ�|l7���l;M�9�\����',���������l[Q��I_	�i����P�`q�(�T&�	���7\��O�1�HV���T%�_���H���!9�fz`�����>r��P�u������oT!�b�&�e���;��W�^���
��)�����l0����t�D��Wf-�U�K�UE�{B�2�}���A�&[�6�����!�
�*�S��;J����:,��B�OOI<sL�H�X�rX��fj:��y���<
�@�����g��\��z�y4�Sq�L�4M�L���Q��ZB��f�7��5���{s��o��M�c38L���rsE;7����}Z��W���%x\_����j���tuCkbi�!)��=wz�a���
>��<���w^��GNr��W�.��A��4���������_L�S-����4��uA�
��(,���g�8����U�x��E�vH�k8�5�R��9��i7->����C^�,V��,>���b���GQ�r����IBY��_
�/��*1�>�>!�ZA�7%�Y<v�x��<h�	0t�a(����yS�zg]�s
���~�a��@k�,%-JiF���������7{4���}|-�E7�}h)/z,%^&��g�6��t�-{�55M4fn��������y ��d��O�����jnbj�������ir�`4��v��}�A�!^v��\�Z����x�AA�?��|'��+��Q�����s9��Dc{i^�A�|���x����vO\�0��B�Zd�F��L6Nhde���i[�\������Ej7Hx$�&��R4vV+��
�)�H�+��\T�me�f;��_�\
=��t��k��������������kvY����!�����+m�u3��%M�,YS�O_�r��Vl��a����y�l��e�ON+��RS+Z�����G]S.�m�,$�8��3������ dIM*������8����W��h�?�p����7�O���o��������o���t]t*��Ne�N�%���fzO��`w1 _�gc\��^5�+P��k��������������M�BZ�C��A�+��Z��l�}J���d}-�]��w����G��f�o6���@|�1y\��T)��R�"��%��X�UC|F�Q h�9���[kG��)��2���m:J�%��,-�
�C~c�; �I�"���F��go�Y�t^
���Ie����������Tp�Y�X�dx�������_����@?�~p��_
���r+�v ���?��k�O��������������{����'X�������83�a�>���F��=�>��=�gR��������/����}�O�
�^����'�1!������b���Xr����|�����T��������57cf|����g��P��	#�l������-���gsv�I!��1��w���Be����h�L�a� ���8����?��?�JZyh_L�1���O.�R����ySiv0��=�}m�|n�gT��~}���3P��lm4k��+7.B7����,���37���@>u@l0�� ���Z�2��>C���~���f.?c����g37��Gx� ,���}���|_��IH��&}&?����q�����=�������^Z�{�:_�0�<�k�Zh:�XY�t���;�;�I\������T�I�lpD�(�|���h�!�����w�.��J�V���Zu�T-�7����CL��6@�����$���
��|x*�M�����&3k��G��&����1�z��u�� �T������������23�D��N��u��8Q|�M��O��6��"VD��x���H��\.�7��/�����u�k��n�#�X���2t&q��� �.��v�"��lE�
h����6�����<�?�~�^*'*mx���q|a_G�q�m��p���/\�3�g�'�|�V����%����Z�P�`3�����l8�6������{��=~�^k��������o��^\�^���bO<*����4]���#��M��Oi���C�7��2�8@5'��[>k(���9>�T�t����2_x���G�<��������G<����G*�F�.5����a��}aB_��&���B���3���]�Vf���U�����L(=��P](*�f�NkC��/<����#�T����o�#��f�l�����G��r�����	=�b���I�a��r���f������N/�J8Q���bl�����q<�b�I�|�"��<�?y��j�J����7	��T;��
=�}'�h���&��O�nY�&�<�w��_���\�o9�/l���"��
m3��1��D��d�I��`�����p���-5�X����-6���zm������c|����2_8�/�0b��R�.����|�����N��o�_�&�����	��$������a>���qa�/���E�C���R���B1�M.*R���g���|A�R�T)�W7����'^b>�U5$�|����gm������A*+������E��������s�����|k�;������p������L��l�#7L�e���<�,!���L7S���_xj���/��"z6���A�/����k�P�$N��M��J�`�t�/='�w���T�X���/��@��w���w��bB��7u�S4�l�]gnV�Q����&��7�P%��F��L�;qG�G����`T�;~D��y������2�!`��;t��&��6+v�U^w��*@�4�G��C�G�7L}�L��=~�q<��h�G��,��D�*�,�9��-\�w��?�`1�
�m'|{w����jN��h�;����L�f�nhC7�
Bz�������
���?��x*c�@(�_ ���������1r�8ocf�p
P��b����-��L_~{�%|���������y�&0���������g�o�*�s����(9ou��1(��7���rC�`���n8q.����L���5��;C@U�@��!�9��)��J�H3!F�f��E3Tdl�;*������%A�����c��wg�.�W��;RO�����������g��d�;�?&�D�Rf�<]�@�KeA�)��(pq�����(������	�����@ �d����7Cj���&>��ftr����/O: H�1�j�����:��i�~������R���mO�����R����~�7�?�.4�c���������m���S���AY<��xpj�AU<h�5x�oxP*�����[��u`�'���=c�3���Y�C� �
`V�b�8�=P]@��U&k=?���|�Di:u�g�XJ']�j~�r�j����o�����������~�S�@��	���H�������U��Qa�^�rb��������~:|n���R[?b+�]G�t�������y����3���������p��?���
i�S�A?�)�r���kYot�'�>u���+�[o�N����6�eCZ����D���k����@[���T�c��p��0W�;`u�N(_����)9��t"k>qf(M�SP4�i@���{B�MR�|$D�(.'t�%F�?����mMo���}S���~g`������EqzHi��QUlk���1Y��;����1��l��;�Fr�����,ySE�� ��o���������5��(��[��}���5ps	���v�1YE������=G�Z���lFq$�������O����D6*m�������J��z�V���dN��4�3�\����.���y���:��N�P���=3�����?Ae��:u���R���31���LHh����Dk����
��������q�}�:7,��C
R�U��b>-������4A����\�����cw�_��<h����~�tt�s�w��;�����Rv-���.2�c�94��\��������dRX��P{�X�����V�L��2�nw���
��C�Z��sQote:��m���	�PZ|�y}|�"5Q��s�>�ftON^���'O�i�HR��Y��7�GE����w3��%m�@���!+k��x�-?�?�A�!8��`���^��)(���2�r�n��u{/����w_���j`l���|c���jm�])NoM���R���ip|�G�
#110Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Kevin Grittner (#108)

On 3 March 2013 13:12, Kevin Grittner <kgrittn@ymail.com> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

[ ... ] led to this thought:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

[Sorry to join this discussion so late]

FWIW I had a quick look at other DBs to see if there were any
other precedents out there. Oracle was the only one I could find
with anything similar. They use the same creation syntax:

CREATE MATERIALIZED VIEW name [options] AS SELECT ...

It is a pretty obvious choice when you look at other SQL
statements.

and they use ALTER for everything else, such as refreshing the
MV:

ALTER MATERIALIZED VIEW name REFRESH [options];

No, that is for specifiying when and under what conditions an
automatic refresh is done. To do an immediate action which is
equivalent to what I have for the REFRESH statement, they use a
REFRESH() function. That seemed too incompatible with how we've
done everything else in PostgreSQL -- I felt that a statement would
make more sense. Consider REINDEX, CLUSTER, and VACUUM FULL for
example.

AFAICT the nearest thing they have to TRUNCATE/DISCARD is:

ALTER MATERIALIZED VIEW name CONSIDER FRESH;

No, that doesn't rebuild or discard data -- if the MV is
out-of-date and therefore unscannable according to the how the MV
has been set up, this overrides that indication and allows scanning
in spite of that.

Ah, OK I see.

I misunderstood what the Oracle docs were saying. ALTER only changes
the MV's definition, whereas their REFRESH() function and your REFRESH
statement updates the data in the MV. That makes much more sense.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#111Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Kevin Grittner (#108)

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Rewriting queries using
expressions which match the MV's query to pull from the MV instead
of the underlying tables is the exception. While that is a "sexy"
feature, and I'm sure one can construct examples where it helps
performance, it seems to me unlikely to be very generally useful.
I suspect that it exists mostly so that people who want to write an
RFP to pick a particular product can include that as a requirement.
In other words, I think the main benefit of automatic rewrite
using an MV is marketing, not technical or performance.

I think that automatically using materialized views even when the
query doesn’t mention them directly, is akin to automatically using
indexes without having to mention them in the query. That way, queries
can be written the natural way, and “creating materialized views” is
an optimization that can be applied by a DBA without having to update
the application queries to use them.

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#112Kevin Grittner
kgrittn@ymail.com
In reply to: Nicolas Barbier (#111)

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Rewriting queries using expressions which match the MV's query
to pull from the MV instead of the underlying tables is the
exception.  While that is a "sexy" feature, and I'm sure one can
construct examples where it helps performance, it seems to me
unlikely to be very generally useful.  I suspect that it exists
mostly so that people who want to write an RFP to pick a
particular product can include that as a requirement.  In other
words, I think the main benefit of automatic rewrite using an MV
is marketing, not technical or performance.

I think that automatically using materialized views even when the
query doesn’t mention them directly, is akin to automatically
using indexes without having to mention them in the query. That
way, queries can be written the natural way, and “creating
materialized views” is an optimization that can be applied by a
DBA without having to update the application queries to use them.

Oh, I understand that concept perfectly well, I just wonder how
often it is useful in practice.  The cost of planning with indexes
tends to go up dramatically the planner needs to evaluate all
possible combinations of access paths.  We've devoted quite a bit
of energy keeping that from being something like the factorial of
the number of indexes.  If you now need to find all materialized
views which could substitute for parts of a query, and look at all
permutations of how those could be used, and which indexes can be
used for each of those combinations, you have planning time which
can explode to extreme levels.

Where the number of database objects are small and their sizes are
large (like some data warehouse situations), you could come out
ahead; and if I wanted to showcase the capability you describe
that's what I would use.  With a large number of database objects
with only a few tens of millions of rows per table, I doubt you
will come out ahead.

Granted, you could say the same thing about indexes, and they are
very often useful.  I'm saying that I expect the usefulness of the
technique you describe is generally very low, but not zero.  Except
for marketing, where it's a flashy feature.  I would be interested
in seeing information to show where it works well, though.  There
is probably something to be learned by looking at the details of
the environment and workload of such a site.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#113Greg Stark
stark@mit.edu
In reply to: Kevin Grittner (#106)

On Sat, Mar 2, 2013 at 3:06 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

(1) Any DML against the MV would need to be limited to some
context fired by the underlying changes. If we allow changes to
the MV outside of that without it being part of some "updateable
MV" feature (reversing the direction of flow of changes), the MV
could not be trusted at all. If you're going to do that, just use
a table.

Oh! I misunderstood what you were suggesting. I think we were talking
at cross-purposes.

You're imagining a user issues truncate against the underlying
table(s) and the code that handles updating the materialized view will
need to issue a truncate against the MV to update it.

I was imagining that you wanted to be able to issue DML against the MV
just as one can against an updateable view. That DML should propagate
to the underlying table(s) through various magic.

It's a pretty theoretical fear now but one day it may be important to
avoid confusion between these two.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#114Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Kevin Grittner (#112)

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

I think that automatically using materialized views even when the
query doesn’t mention them directly, is akin to automatically
using indexes without having to mention them in the query. That
way, queries can be written the natural way, and “creating
materialized views” is an optimization that can be applied by a
DBA without having to update the application queries to use them.

Oh, I understand that concept perfectly well, I just wonder how
often it is useful in practice. The cost of planning with indexes
tends to go up dramatically the planner needs to evaluate all
possible combinations of access paths. We've devoted quite a bit
of energy keeping that from being something like the factorial of
the number of indexes. If you now need to find all materialized
views which could substitute for parts of a query, and look at all
permutations of how those could be used, and which indexes can be
used for each of those combinations, you have planning time which
can explode to extreme levels.

I guess that a basic version of such a feature would do something like this:

(1) Check for each matview whether it simply consists of an optional
bunch of joins + optional aggregation + optional general filter
conditions (to support something akin to a partial index). If not, the
optimization doesn’t take this matview into account. This step can be
done beforehand.
(2) Check for each (sub)query in the query-to-optimize whether it does
the following (at a smart point in the optimization phase). If any of
these conditions are not met, don’t use this matview:
- Joins at least the tables that are joined in the matview.
- Contains join conditions and general filter conditions that are at
least as strict.
- Doesn’t refer elsewhere to any attributes that the matview doesn’t contain.
(3) Always replace the corresponding query parts with the matview
(i.e., assume that the cost will always be lower than performing the
original query).
(4) If multiple matviews fit in step 3, try them all (and use the one
that yields the lower total cost).
(5) Always replace any aggregation with the corresponding
aggregation-results (if they exist) from the matview.

That doesn’t sound as if it would make planning time explode that much
(except, because of step 4, when there are many matviews that contain
overlapping sets of joined tables, and a query joins over the union of
those sets, *and* the replaceable-by-matviews parts are in
subqueries). It could even decrease it significantly (e.g., in the
case where a bunch of joins would be replaced with a scan of a
matview).

Also, I suppose that once such functionality exists, application
writers would be more inclined to write “heavy” queries that do lots
of aggregation even in an OLTP environment, of the kind that is these
days typically only done in OLAP environments.

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#115Tom Lane
tgl@sss.pgh.pa.us
In reply to: Nicolas Barbier (#114)

Nicolas Barbier <nicolas.barbier@gmail.com> writes:

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

I think that automatically using materialized views even when the
query doesn’t mention them directly, is akin to automatically
using indexes without having to mention them in the query.

Oh, I understand that concept perfectly well, I just wonder how
often it is useful in practice.

There's a much more fundamental reason why this will never happen, which
is that the query planner is not licensed to decide that you only want
an approximate and not an exact answer to your query.

If MVs were guaranteed always up-to-date, maybe we could think about
automatic use of them --- but that's a far different feature from what
Kevin has here.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#116Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#115)

There's a much more fundamental reason why this will never happen, which
is that the query planner is not licensed to decide that you only want
an approximate and not an exact answer to your query.

I think it would be worth talking about when someone wants to implement
it. I'd imagine it would require setting a GUC, though, which would be
off by default for obvious reasosn.

If MVs were guaranteed always up-to-date, maybe we could think about
automatic use of them --- but that's a far different feature from what
Kevin has here.

And of limited utility, as mentioned.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#117Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#109)

Kevin Grittner <kgrittn@ymail.com> wrote:

I'm still working on docs, and the changes related to the syntax
change are still only lightly tested, but as far as I know, all is
complete except for the docs.  I'm still working on those and
expect to have them completed late today.  I'm posting this patch
to allow a chance for final review of the code changes before I
push.

Pushed.

I'll be keeping an eye on the buildfarm.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#118Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#117)
Re: [HACKERS] Materialized views WIP patch

Kevin Grittner <kgrittn@ymail.com> wrote:

Pushed.

My first attempt to push it failed because of a concurrent commit
by Tom, and then when I went to redo it I accidentally included a
file with the .orig suffix.  I think I've undone my error, but
github doesn't seem to be updating, so I fear I did some damage
somehow. If someone who knows what they are doing with git better
than I do could check the server to make sure there is no residual
problem, I would appreciate it.

Sorry for the trouble.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-committers mailing list (pgsql-committers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-committers

#119Craig Ringer
craig@2ndquadrant.com
In reply to: Josh Berkus (#116)

On 03/04/2013 08:27 AM, Josh Berkus wrote:

There's a much more fundamental reason why this will never happen, which
is that the query planner is not licensed to decide that you only want
an approximate and not an exact answer to your query.

I think it would be worth talking about when someone wants to implement
it. I'd imagine it would require setting a GUC, though, which would be
off by default for obvious reasosn.

I'm not a fan of this, even with a GUC. Imagine doing remote debugging
by email/phone. There are enough things to check already ("does your
application REALLY commit that transaction?") without also having to
deal with settings that can cause a potentially out of date view of the
data to be used without it being visible in the query its self.

I hate to even say it, but this is where a per-query [redacted] would be
good, so we could say in the query text that this query may use matviews
that are not perfectly up to date.

At this point it's all hand-waving anyway, since no feature to allow the
planner to automatically rewrite a subtree of a query to use a matview
instead exists.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#120Euler Taveira
euler@timbira.com
In reply to: Josh Berkus (#116)

On 03-03-2013 21:27, Josh Berkus wrote:

I think it would be worth talking about when someone wants to implement
it. I'd imagine it would require setting a GUC, though, which would be
off by default for obvious reasosn.

-1. Why not adding another storage_parameter, say auto_refresh=on? Also,
that's another feature.

And of limited utility, as mentioned.

For a first release, that is fine as is. Let's not complicate a feature that
has been widely discussed in this development cycle.

--
Euler Taveira de Oliveira - Timbira http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#121Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Kevin Grittner (#118)
Re: [HACKERS] Materialized views WIP patch

Kevin Grittner wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

Pushed.

My first attempt to push it failed because of a concurrent commit
by Tom, and then when I went to redo it I accidentally included a
file with the .orig suffix.  I think I've undone my error, but
github doesn't seem to be updating, so I fear I did some damage
somehow. If someone who knows what they are doing with git better
than I do could check the server to make sure there is no residual
problem, I would appreciate it.

FWIW this email was stuck in the moderation queue, I just released it.
We might have realized earlier that there was a problem ...

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

--
Sent via pgsql-committers mailing list (pgsql-committers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-committers

#122Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#115)

On 3 March 2013 23:39, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Nicolas Barbier <nicolas.barbier@gmail.com> writes:

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

I think that automatically using materialized views even when the
query doesn’t mention them directly, is akin to automatically
using indexes without having to mention them in the query.

Oh, I understand that concept perfectly well, I just wonder how
often it is useful in practice.

There's a much more fundamental reason why this will never happen, which
is that the query planner is not licensed to decide that you only want
an approximate and not an exact answer to your query.

If MVs were guaranteed always up-to-date, maybe we could think about
automatic use of them --- but that's a far different feature from what
Kevin has here.

Its not a different feature, its what most people expect a feature
called MV to deliver. That's not a matter of opinion, its simply how
every other database works currently - Oracle, Teradata, SQLServer at
least. The fact that we don't allow MVs to automatically optimize
queries is acceptable, as long as that is clearly marked in some way
to avoid confusion, and I don't mean buried on p5 of the docs. What we
have here is a partial implementation that can be improved upon over
next few releases. I hope anyone isn't going to claim that
"Materialized Views" have been implemented in the release notes in
this release, because unqualified that would be seriously misleading
and might even stifle further funding to improve things to the level
already implemented elsewhere. Just to reiterate, I fully support the
committing of this partial feature into Postgres in this release
because it will be a long haul to complete the full feature and what
we have here is a reasonable stepping stone to get there.

Transactionally up-yo-date MVs can be used like indexes in the
planner. The idea that this is impossible because of the permutations
involved is somewhat ridiculous; there is much published work on
optimising that and some obvious optimisations. Clearly that varies
according to the number of MVs and the number of tables they touch,
not the overall complexity of the query. The overhead is probably same
or less as partial indexes, which we currently think is acceptable. In
any case, if you don't wish that overhead, don't use MVs.

Non-transactionally up-to-date MVs could also be used like indexes in
the planner, if we gave the planner the "licence" it (clearly) lacks.
If using MV makes a two-hour query return in 1 minute, then using an
MV that is 15 minutes out of date is likely to be a win. The "licence"
is some kind of user parameter/option that specifies how stale an
answer a query can return. For many queries that involve averages and
sums, a stale or perhaps an approximate answer would hardly differ
anyway. So I think there is room somewhere there for a "staleness"
time specification by the user, allowing approximation.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#123Kevin Grittner
kgrittn@ymail.com
In reply to: Simon Riggs (#122)

Simon Riggs <simon@2ndQuadrant.com> wrote:

On 3 March 2013 23:39, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Nicolas Barbier <nicolas.barbier@gmail.com> writes:

2013/3/3 Kevin Grittner <kgrittn@ymail.com>:

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

I think that automatically using materialized views even when
the query doesn’t mention them directly, is akin to
automatically using indexes without having to mention them in
the query.

Oh, I understand that concept perfectly well, I just wonder
how often it is useful in practice.

There's a much more fundamental reason why this will never
happen, which is that the query planner is not licensed to
decide that you only want an approximate and not an exact answer
to your query.

If MVs were guaranteed always up-to-date, maybe we could think
about automatic use of them --- but that's a far different
feature from what Kevin has here.

Its not a different feature, its what most people expect a
feature called MV to deliver. That's not a matter of opinion, its
simply how every other database works currently - Oracle,
Teradata, SQLServer at least. The fact that we don't allow MVs to
automatically optimize queries is acceptable, as long as that is
clearly marked in some way to avoid confusion, and I don't mean
buried on p5 of the docs. What we have here is a partial
implementation that can be improved upon over next few releases.
I hope anyone isn't going to claim that "Materialized Views" have
been implemented in the release notes in this release, because
unqualified that would be seriously misleading and might even
stifle further funding to improve things to the level already
implemented elsewhere. Just to reiterate, I fully support the
committing of this partial feature into Postgres in this release
because it will be a long haul to complete the full feature and
what we have here is a reasonable stepping stone to get there.

Transactionally up-yo-date MVs can be used like indexes in the
planner. The idea that this is impossible because of the
permutations involved is somewhat ridiculous; there is much
published work on optimising that and some obvious optimisations.
Clearly that varies according to the number of MVs and the number
of tables they touch, not the overall complexity of the query.
The overhead is probably same or less as partial indexes, which
we currently think is acceptable. In any case, if you don't wish
that overhead, don't use MVs.

Non-transactionally up-to-date MVs could also be used like
indexes in the planner, if we gave the planner the "licence" it
(clearly) lacks. If using MV makes a two-hour query return in 1
minute, then using an MV that is 15 minutes out of date is likely
to be a win. The "licence" is some kind of user parameter/option
that specifies how stale an answer a query can return. For many
queries that involve averages and sums, a stale or perhaps an
approximate answer would hardly differ anyway. So I think there
is room somewhere there for a "staleness" time specification by
the user, allowing approximation.

I don't think I disagree with any of what Simon says other than his
feelings about the planning cost.  Imagine that there are ten MVs
that might apply to a complex query, but some of them are mutually
exclusive, so there are a large number of permutations of MVs which
could be used to replace parts of the original query.  And maybe
some of base tables have indexes which could reduce execution cost
which aren't present in some or all of the MVs.  And each MV has a
number of indexes.  The combinatorial explosion of possible plans
would make it hard to constrain plan time without resorting to some
crude rules about what to choose.  That's not an unsolvable
problem, but I see it as a pretty big problem.

I have no doubt that someone could take a big data warehouse and
add one or two MVs and show a dramatic improvement in the run time
of a query.  Almost as big as if the query were rewritten to usee
the MV directly.  It would make for a very nice presentation, and
as long as they are used sparingly this could be a useful tool for
a data warehouse environment which is playing with alternative ways
to optimize slow queries which pass a lot of data.  In other
environments, I feel that it gets a lot harder to show a big win.

The good news is that it sounds like we agree on the ideal
long-term feature set.  I'm just a lot more excited, based on the
use-cases I've seen, about the addition of incremental updates than
substituting MVs into query plans which reference the underlying
tables.  Perhaps that indicates a chance to the final feature set
sooner, through everyone scratching their own itches.  :-)

And we both seem to feel that some system for managing acceptable
levels of MV "freshness" is a necessary feature in order to go very
much further.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#124Simon Riggs
simon@2ndquadrant.com
In reply to: Kevin Grittner (#123)

On 5 March 2013 12:15, Kevin Grittner <kgrittn@ymail.com> wrote:

I don't think I disagree with any of what Simon says other than his
feelings about the planning cost. Imagine that there are ten MVs
that might apply to a complex query, but some of them are mutually
exclusive, so there are a large number of permutations of MVs which
could be used to replace parts of the original query. And maybe
some of base tables have indexes which could reduce execution cost
which aren't present in some or all of the MVs. And each MV has a
number of indexes. The combinatorial explosion of possible plans
would make it hard to constrain plan time without resorting to some
crude rules about what to choose. That's not an unsolvable
problem, but I see it as a pretty big problem.

If we are proposing that we shouldn't try to optimise because its not
usefully solvable, then I would disagree.

If we are saying that more plans are possible with MVs, then I'd say,
yes there *could* be - that's the one of the purposes. That represents
more options for optimisation and we should be happy, not sad about
that. Yes, we would need some thought to how those potential
optimisations can be applied without additional planning cost, but I
see that as a long term task, not a problem. The question is will the
potential for additional planning time actually materialise into a
planning problem? (See below).

I have no doubt that someone could take a big data warehouse and
add one or two MVs and show a dramatic improvement in the run time
of a query. Almost as big as if the query were rewritten to usee
the MV directly. It would make for a very nice presentation, and
as long as they are used sparingly this could be a useful tool for
a data warehouse environment which is playing with alternative ways
to optimize slow queries which pass a lot of data. In other
environments, I feel that it gets a lot harder to show a big win.

Are there realistically going to be more options to consider? In
practice, no, because in most cases we won't be considering both MVs
and indexes.

Splatting MVs on randomly won't show any more improvement than
splatting indexes on randomly helps. Specific optimisations help in
specific cases only. And of course, adding extra data structures that
have no value certainly does increase planning time. Presumably we'd
need some way of seeing how frequently MVs were picked, so we could
drop unused MVs just like we can indexes.

* Indexes are great at speeding up various kinds of search queries. If
you don't have any queries like that, they help very little.

* MVs help in specific and restricted use cases, but can otherwise be
thought of as a new kind of index structure. MVs help with joins and
aggregations, so if you don't do much of that, you'll see no benefit.

That knowledge also allows us to develop heuristics for sane
optimisation. If the MV has a GROUP BY in it, and a query doesn't,
then it probably won't help much to improve query times. If it
involves a join you aren't using, then that won't help either. My
first guess would be that we don't even bother looking for MV plans
unless it has an aggregated result, or a large scale join. We do
something similar when we look for plans that use indexes when we have
appropriate quals - no quals, no indexes.

As a result, I don't see MVs increasing planning times for requests
that would make little or no use of them. There will be more planning
time on queries that could make use of them and that is good because
we really care about that.

Sure, a badly written planner might cost more time than it saves. All
of this work requires investment from someone with the time and/or
experience to make a good go at it. I'm not pushing Tom towards it,
nor anyone else, but I do want to see the door kept open for someone
to do this when possible (i.e. not GSoC).

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#125Josh Berkus
josh@agliodbs.com
In reply to: Simon Riggs (#124)

Simon, Kevin, all:

Actually, there was already an attempt at automated MV query planning as
a prior university project. We could mine that for ideas.

Hmmm. I thought it was on pgfoundry, but it's not. Does anyone have
access to ACM databases etc. so they could search for this?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#126Robert Haas
robertmhaas@gmail.com
In reply to: Kevin Grittner (#123)

On Tue, Mar 5, 2013 at 7:15 AM, Kevin Grittner <kgrittn@ymail.com> wrote:

I don't think I disagree with any of what Simon says other than his
feelings about the planning cost. Imagine that there are ten MVs
that might apply to a complex query, but some of them are mutually
exclusive, so there are a large number of permutations of MVs which
could be used to replace parts of the original query. And maybe
some of base tables have indexes which could reduce execution cost
which aren't present in some or all of the MVs. And each MV has a
number of indexes. The combinatorial explosion of possible plans
would make it hard to constrain plan time without resorting to some
crude rules about what to choose. That's not an unsolvable
problem, but I see it as a pretty big problem.

I'm not sure I agree. Suppose you have a query like SELECT * FROM a
INNER JOIN b ON a.x = b.x INNER JOIN c ON a.y = c.y WHERE <some
stuff>. The query planner will construct paths for scans on a, b, and
c. Then it will construct joinrels for (a b), (a c), (b c), and
eventually (a b c) and calculate a set of promising paths for each of
them. If there is a materialized view available for one of those
joinrels, all we really need to do is add the possible paths for
scanning that materialized view to the joinrel. They'll either be
better than the existing paths, or they won't. If they're better, the
paths that otherwise would have gotten chosen will get kicked out. If
they're worse, the materialized-view paths will get kicked out.
Either way, we don't have any more paths than we would have otherwise
- so no combinatorial explosion.

There is one case where we might end up with more paths than we had
before. Suppose there's a materialized view on the query SELECT a.x,
a.y, a.z, b.t FROM a INNER JOIN b ON a.x = b.x ORDER BY a.z and the
users enters just that query. Suppose further that the materialized
view has an index on column z, but table a does not. In that case,
the best paths for the joinrel (a b) not involving the materialized
view will be an unordered path, but we could scan the materialized
view using the index and so will have a path that is ordered by a.z to
add to the joinrel. This path will stick around even if it's more
expensive than the unordered path because we know it avoids a final
sort. So in that case we do have more paths, but they are potentially
useful paths, so I don't see that as a problem.

It seems to me that the tricky part of this is not that it might add a
lot more paths (because I don't think it will, and if it does I think
they're useful paths), but that figuring out whether or not a
materialized view matches any particular joinrel might be expensive.
I think we're going to need a pretty accurate heuristic for quickly
discarding materialized views that can't possibly be relevant to the
query as a whole, or to particular joinrels. There are a couple of
possible ways to approach that. The most manual of these is probably
to have a command like ALTER TABLE x {ENABLE | DISABLE } REWRITE
MATERIALIZED y, where the user has to explicitly ask for materialized
views to be considered, or else they aren't. That sort of
fine-grained control might have other benefits as well.

I think a more automated solution is also possible, if we want it.
For a materialized view to match a query, all of the baserels in the
materialized view must also be present in the query. (Actually, there
are situations where this isn't true; e.g. the materialized view has
an extra table, but it's joined in a way that could be pruned away by
the join removal code, but I think we could ignore such
somewhat-pathological cases at least initially.) It seems to me that
if we could figure out a very-cheap way to throw away all of the
materialized views that don't pass that basic test, we'd be reasonably
close to a workable solution. A database with tons of materialized
views defined on the same set of target relations might still have
some planning time problems, but so might a database with tons of
indexes.

In that regard, what I was thinking about is to use something sort of
like a Bloom filter. Essentially, produce a "fingerprint" for each
materialized view. For the sake of argument, let's say that the
fingerprint is a 64-bit integer, although it could be a bit string of
any length. To construct the fingerprint, hash the OID of each
relation involved in the view onto a bit position between 0 and 63.
Set the bits for all relations involved in the materialized view.
Then, construct a fingerprint for the query in the same way. Any
materialized view where (matview_fingerprint & query_fingerprint) !=
matview_fingerprint needn't be considered; moreover, for a given
joinrel, any materialized view where matview_fingerprint !=
joinrel_fingerprint (computed using the same scheme) needn't be
considered. Of course, a matching fingerprint doesn't mean that the
materialized view matches, or even necessarily that it involves the
correct set of relations, so there's a lot more double-checking that
has to be done afterwards before you can decide that the view is in
fact a match, but at least it's a fast way to throw away some large
percentage of materialized views that are definitely NOT relevant to
the query, and only do more detailed evaluation on the ones that might
be matches. The chances of false positives can be reduced if you care
to make the fingerprints bigger (and thus more expensive to compare).

All that having been said, it's hard for me to imagine that anyone
really cares about any of this until we have an incremental update
feature, which right now we don't. Actually, I'm betting that's going
to be significantly harder than automatic-query-rewrite, when all is
said and done. Automatic-query-rewrite, if and when we get it, will
not be easy and will require a bunch of work from someone with a good
understanding of the planner, but it strikes me as the sort of thing
that might work out to one large project and then it's done. Whereas,
incremental update sounds to me like a series of projects over a
series of releases targeting various special cases, where we can
always point to some improvements vs. release N-1 but we're never
actually done and able to move on to the next thing. As a roadmap
goes, I think that's OK. Even a reasonably simplistic and partial
implementation of incremental update will benefit a lot of users. But
in terms of relative difficulty, it's not at all obvious to me that
that's the easier part of the project.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#127Kevin Grittner
kgrittn@ymail.com
In reply to: Josh Berkus (#125)

Josh Berkus <josh@agliodbs.com> wrote:

There is no shortage of literature on the topic, although any
papers from the ACM could certainly be of interest due to the
general high quality of papers published there.  Adding anything
like this to 9.3 is clearly out of the question, though, so I
really don't want to spend time researching this now, or
encouraging others to do so until after we have a 9.3 release
candidate.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#128Kevin Grittner
kgrittn@ymail.com
In reply to: Robert Haas (#126)

Robert Haas <robertmhaas@gmail.com> wrote:

All that having been said, it's hard for me to imagine that
anyone really cares about any of this until we have an
incremental update feature, which right now we don't.

These are actually independent of one another, as long as we nail
down how we're determining "freshness" -- which is probably needed
for either.  Someone who's immersed in tuning long-running DW
queries might be interested in this before incremental update.
(They might load the data once per month, so refreshing the MVs as
a step in that process might be cheaper than incrementally
maintaining them.)  Someone could base "freshness" on
pg_relation_is_scannable() and start working on automatic query
rewrite right now, if they wanted to.

Actually, I'm betting that's going to be significantly harder
than automatic-query-rewrite, when all is said and done.
Automatic-query-rewrite, if and when we get it, will not be easy
and will require a bunch of work from someone with a good
understanding of the planner, but it strikes me as the sort of
thing that might work out to one large project and then it's
done.

I still think we're going to hit the wall on planning time under
certain circumstances and need to tweak that over the course of
several releases, but now is not the time to get into the details
of why I think that.  We've spent way too much time on it already
for the point we're at in the 9.3 cycle.  I've kept my concerns
hand-wavy on purpose, and am trying hard to resist the temptation
to spend a lot of time demonstrating the problems.

Whereas, incremental update sounds to me like a series of
projects over a series of releases targeting various special
cases, where we can always point to some improvements vs. release
N-1 but we're never actually done

Exactly.  I predict that we will eventually have some special sort
of trigger for maintaining MVs based on base table changes to
handle the ones that are just too expensive (in developer time or
run time) to fully automate.  But there is a lot of low-hanging
fruit for automation.

Even a reasonably simplistic and partial implementation of
incremental update will benefit a lot of users.

Agreed.

But in terms of relative difficulty, it's not at all obvious to
me that that's the easier part of the project.

I totally agree that getting something working to use MVs in place
of underlying tables is not all that different or more difficult
than using partial indexes.  I just predict that we'll get a lot of
complaints about cases where it results in worse performance and
we'll need to deal with those issues.  I don't seem that as being
brain surgery; just a messy matter of trying to get this pretty
theory to work well in the real world -- probably using a bunch of
not-so-elegant heuristics.  And in the end, the best you can hope
for is performance not noticeably worse than you would get if you
modified your query to explicitly use the MV(s) -- you're just
saving yourself the rewrite.  Well, OK, there is the point that,
(like indexes) if you run the query which hits the base tables with
different parameters, and a new plan is generated each time, it
might pick different MVs or exclude them as is most efficient for
the given parameters.  That's the Holy Grail of all this.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#129Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Robert Haas (#126)

2013/3/5 Robert Haas <robertmhaas@gmail.com>:

All that having been said, it's hard for me to imagine that anyone
really cares about any of this until we have an incremental update
feature, which right now we don't. Actually, I'm betting that's going
to be significantly harder than automatic-query-rewrite, when all is
said and done.

I agree.

E.g., things such as keeping a matview consistent relative to changes
applied to the base tables during the same transaction, might be
mightily difficult to implement in a performant way. OTOH, matviews
that can only be used for optimization if their base tables were not
changed “too recently” (e.g., by transactions that are still in
flight, including the current transaction), are probably kind of
useful in themselves as long as those base tables are not updated all
the time.

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#130Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#126)

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Mar 5, 2013 at 7:15 AM, Kevin Grittner <kgrittn@ymail.com> wrote:

I don't think I disagree with any of what Simon says other than his
feelings about the planning cost.

I'm not sure I agree. Suppose you have a query like SELECT * FROM a
INNER JOIN b ON a.x = b.x INNER JOIN c ON a.y = c.y WHERE <some
stuff>. The query planner will construct paths for scans on a, b, and
c. Then it will construct joinrels for (a b), (a c), (b c), and
eventually (a b c) and calculate a set of promising paths for each of
them. If there is a materialized view available for one of those
joinrels, all we really need to do is add the possible paths for
scanning that materialized view to the joinrel.

That only works to the extent that a materialized view can be described
by a path. My impression is that most of the use-cases for MVs will
involve aggregates or similar data reduction operators, and we don't
currently implement anything about aggregates at the Path level.
Arguably it would be useful to do so; in particular, we could get rid
of the currently hard-wired mechanism for choosing between sorted and
hashed aggregation, and perhaps there'd be a less grotty way to deal
with index-optimized MIN/MAX aggregates. But there's a great deal to do
to make that happen, and up to now I haven't seen any indication that it
would do much except add overhead.

FWIW, my opinion is that doing anything like this in the planner is
going to be enormously expensive. Index matching is already pretty
expensive, and that has the saving grace that we only do it once per
base relation. Your sketch above implies trying to match to MVs once
per considered join relation, which will be combinatorially worse.
Even with a lot of sweat over reducing the cost of the matching, it
will hurt.

All that having been said, it's hard for me to imagine that anyone
really cares about any of this until we have an incremental update
feature, which right now we don't.

Agreed. Even if we're willing to have an "approximate results are OK"
GUC (which frankly strikes me as a horrid idea), people would certainly
not be willing to turn it on without some guarantee as to how stale the
results could be.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#131Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Kevin Grittner (#128)

2013/3/5 Kevin Grittner <kgrittn@ymail.com>:

Exactly. I predict that we will eventually have some special sort
of trigger for maintaining MVs based on base table changes to
handle the ones that are just too expensive (in developer time or
run time) to fully automate. But there is a lot of low-hanging
fruit for automation.

I think it would be totally OK to restrict the possible definitions
for matviews that can be maintained fully incrementally to something
like:

SELECT attributes and aggregations FROM trivial joins WHERE trivial
condition GROUP BY attributes;

Those definitions are the most useful for optimizing the things that
matviews are good at (joins and aggregation).

Nicolas

PS. Sorry for having fired off this discussion that obviously doesn’t
really relate to the current patch.

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#132Kevin Grittner
kgrittn@ymail.com
In reply to: Nicolas Barbier (#131)

Nicolas Barbier <nicolas.barbier@gmail.com> wrote:

PS. Sorry for having fired off this discussion that obviously
doesn’t really relate to the current patch.

I know it's hard to resist.  While I think there will be a number
of people for whom the current patch will be a convenience and will
therefore use it, it is hard to look at what's there and *not* go
"if only it also..."

Perhaps it would be worth looking for anything in the patch that
you think might be painting us into a corner where it would be hard
to do all the other cool things.  While it's late enough in the
process that changing anything like that which you find would be
painful, it might be a lot more painful later if we release without
doing something about it.  My hope, of course, is that you won't
find any such thing.  With this patch I've tried to provide a
minimal framework onto which these other things can be bolted.
I've tried hard not to do anything which would make it hard to
extend, but new eyes may see something I missed.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#133Josh Berkus
josh@agliodbs.com
In reply to: Kevin Grittner (#127)

On 03/05/2013 01:09 PM, Kevin Grittner wrote:

Josh Berkus <josh@agliodbs.com> wrote:

There is no shortage of literature on the topic, although any
papers from the ACM could certainly be of interest due to the
general high quality of papers published there. Adding anything
like this to 9.3 is clearly out of the question, though, so I
really don't want to spend time researching this now, or
encouraging others to do so until after we have a 9.3 release
candidate.

Good point.

Just FYI: once we start work on 9.4, some university team got planner
stuff for matviews working with postgres, using version 8.0 or something.

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#134Tatsuo Ishii
ishii@postgresql.org
In reply to: Kevin Grittner (#109)

Kevin Grittner <kgrittn@ymail.com> wrote:

REFRESH MATERIALIZED VIEW name [, ...] WITH [ NO ] DATA

Given the short time, I left out the "[, ...]".  If people think
that is important to get into this release, a follow-on patch might
be possible.

Barring objections, I will use the above and push tomorrow.

I'm still working on docs, and the changes related to the syntax
change are still only lightly tested, but as far as I know, all is
complete except for the docs.  I'm still working on those and
expect to have them completed late today.  I'm posting this patch
to allow a chance for final review of the code changes before I
push.

Was the remaining work on docs done? I would like to test MVs and am
waiting for the docs completed.
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#135Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#130)

On 5 March 2013 22:02, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, my opinion is that doing anything like this in the planner is
going to be enormously expensive. Index matching is already pretty
expensive, and that has the saving grace that we only do it once per
base relation. Your sketch above implies trying to match to MVs once
per considered join relation, which will be combinatorially worse.
Even with a lot of sweat over reducing the cost of the matching, it
will hurt.

As we already said: no MVs => zero overhead => no problem. It costs in
the cases where time savings are possible and not otherwise. The type
of queries and their typical run times are different to the OLTP case,
so any additional planning time is likely to be acceptable. I'm sure
we can have a deep disussion about how to optimise the planner for
this; I'm pretty sure there are reasonable answers to the not-small
difficulties along that road.

Most importantly, I want to make sure we don't swing the door shut on
improvements here, especially if you (Tom) are not personally
convinced enough to do the work yourself.

Making transactional MVs work would be in part justified by the
possible existence of automatic lookaside planning, so yes, the two
things are not linked but certainly closely related.

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#136Tom Lane
tgl@sss.pgh.pa.us
In reply to: Simon Riggs (#135)

Simon Riggs <simon@2ndQuadrant.com> writes:

On 5 March 2013 22:02, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, my opinion is that doing anything like this in the planner is
going to be enormously expensive.

As we already said: no MVs => zero overhead => no problem.

Well, in the first place that statement is false on its face: we'll
still spend cycles looking for relevant MVs, or at least maintaining a
complexly-indexed cache that helps us find out that there are none in
a reasonable amount of time. In the second place, even if it were
approximately true it wouldn't help the people who were using MVs.

It costs in
the cases where time savings are possible and not otherwise.

And that is just complete nonsense: matching costs whether you find a
match or not. Could we have a little less Pollyanna-ish optimism and
a bit more realism about the likely cost of such a feature?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#137Kevin Grittner
kgrittn@ymail.com
In reply to: Tatsuo Ishii (#134)

Tatsuo Ishii <ishii@postgresql.org> wrote:

Was the remaining work on docs done? I would like to test MVs and
am waiting for the docs completed.

I think they are done.  If you notice anything missing or in need
of clarification please let me know.  At this point missing docs
would be a bug in need of a fix.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#138Peter Eisentraut
peter_e@gmx.net
In reply to: Peter Eisentraut (#23)

Kevin,

I haven't seen a reply to this. Were you able to give my notes below
any consideration?

On 2/15/13 12:44 PM, Peter Eisentraut wrote:

On 1/25/13 1:00 AM, Kevin Grittner wrote:

New patch rebased, fixes issues raised by Thom Brown, and addresses
some of your points.

This patch doesn't apply anymore, so I just took a superficial look. I
think the intended functionality and the interfaces look pretty good.
Documentation looks complete, tests are there.

I have a couple of notes:

* What you call WITH [NO] DATA, Oracle calls BUILD IMMEDIATE/DEFERRED.
It might be better to use that as well then.

* You use fields named relkind in the parse nodes, but they don't
actually contain relkind values, which is confusing. I'd just name the
field is_matview or something.

* More generally, I wouldn't be so fond of combining the parse handling
of CREATE TABLE AS and CREATE MATERIALIZED VIEW. They are similar, but
then again so are a lot of other things.

* Some of the terminology is inconsistent. A materialized view is
sometimes called valid, populated, or built, with approximately the same
meaning. Personally, I would settle on "built", as per above, but it
should be one term only.

* I find the name of the relisvalid column a bit confusing. Especially
because it only applies to materialized views, and there is already a
meaning of "valid" for indexes. (Recall that indexes are also stored in
pg_class, but they are concerned about indisvalid.) I would name it
something like relmvbuilt.

Btw., half of the patch seems to consist of updating places referring to
relkind. Is something wrong with the meaning of relkind that this sort
of thing is required? Maybe these places should be operating in terms
of features, not accessing relkind directly.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#139Greg Stark
stark@mit.edu
In reply to: Robert Haas (#126)

On Tue, Mar 5, 2013 at 9:08 PM, Robert Haas <robertmhaas@gmail.com> wrote:

All that having been said, it's hard for me to imagine that anyone
really cares about any of this until we have an incremental update
feature, which right now we don't. Actually, I'm betting that's going
to be significantly harder than automatic-query-rewrite, when all is
said and done. Automatic-query-rewrite, if and when we get it, will
not be easy and will require a bunch of work from someone with a good
understanding of the planner, but it strikes me as the sort of thing
that might work out to one large project and then it's done. Whereas,
incremental update sounds to me like a series of projects over a
series of releases targeting various special cases, where we can
always point to some improvements vs. release N-1 but we're never
actually done and able to move on to the next thing. As a roadmap
goes, I think that's OK. Even a reasonably simplistic and partial
implementation of incremental update will benefit a lot of users. But
in terms of relative difficulty, it's not at all obvious to me that
that's the easier part of the project.

While true that's true for a lot of Postgres features. The only ones
that are one-shot projects are buried deep in the internals. Anything
with UI implications inevitably has limitations and then other people
come along and and work on removing or extending those features.

I do agree with Tom though -- the most frequently asked for
materialized view in the past has always been "select count(*) from
tab". People assume we already do this and are surprised when we
don't. The cookie cutter solution for it is basically exactly what a
incrementally updated materialized view solution would look like
(with the queue of updates with transacion information that are
periodically flattened into the aggregate). Rewriting this might be a
bit tricky and require heuristics to determine just how much work to
expend trying to match materialized views, this type of view would be
where most of the win would be.

I also can't see implementing query rewriting for
non-transactionally-accurate materialized views. If people want a
snapshot of the data that may be out of date that's great. I can tons
of use cases for that. But then surely they won't be surprised to have
to query the snapshot explicitly. If can't see going to all this
trouble to implement transactions and snapshots and wal logging and so
on and then silently rewriting queries to produce data that is not up
to date. I think users would be surprised to find bog-standard SQL
occasionally producing "incorrect" results.

That said, there are cases where snapshots might be up to date even
though we don't implement any incremental updates. If the underlying
data is read-only or hasn't received any update commits since the
snapshot was taken then it might still be useful. There are tons of
ETL applications where you load the data once and then build MVs for
it and never touch the underlying data again.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#140Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#137)

Kevin Grittner <kgrittn@ymail.com> wrote:

Tatsuo Ishii <ishii@postgresql.org> wrote:

Was the remaining work on docs done? I would like to test MVs and
am waiting for the docs completed.

I think they are done.  If you notice anything missing or in need
of clarification please let me know.  At this point missing docs
would be a bug in need of a fix.

I decided to take another pass through to try to spot anyplace I
might have missed.  I found that I had missed documenting the new
pg_matviews system view, so I have just pushed that.

I also think that something should be done about the documentation
for indexes.  Right now that always refers to a "table".  It would
clearly be awkward to change that to "table or materialized view"
everywhere.  I wonder if most of thosse should be changed to
"relation" with a few mentions that the relation could be a table
or a materialized view, or whether some less intrusive change would
be better.  Opinions welcome.

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#141David E. Wheeler
david@justatheory.com
In reply to: Kevin Grittner (#140)

On Mar 6, 2013, at 1:51 PM, Kevin Grittner <kgrittn@ymail.com> wrote:

I also think that something should be done about the documentation
for indexes. Right now that always refers to a "table". It would
clearly be awkward to change that to "table or materialized view"
everywhere. I wonder if most of thosse should be changed to
"relation" with a few mentions that the relation could be a table
or a materialized view, or whether some less intrusive change would
be better. Opinions welcome.

Isn’t a materialized view really just a table that gets updated periodically? And isn’t a non-matierialized view also thought of as a “relation”?

If the answer to both those questions is “yes,” I think the term should remain “table,” with a few mentions that the term includes materialized views (and excludes foreign tables).

Best,

David

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#142Tatsuo Ishii
ishii@postgresql.org
In reply to: Kevin Grittner (#140)

Kevin Grittner <kgrittn@ymail.com> wrote:

Tatsuo Ishii <ishii@postgresql.org> wrote:

Was the remaining work on docs done? I would like to test MVs and
am waiting for the docs completed.

I think they are done.  If you notice anything missing or in need
of clarification please let me know.  At this point missing docs
would be a bug in need of a fix.

Ok.

I decided to take another pass through to try to spot anyplace I
might have missed.  I found that I had missed documenting the new
pg_matviews system view, so I have just pushed that.

I also think that something should be done about the documentation
for indexes.  Right now that always refers to a "table".  It would
clearly be awkward to change that to "table or materialized view"
everywhere.  I wonder if most of thosse should be changed to
"relation" with a few mentions that the relation could be a table
or a materialized view, or whether some less intrusive change would
be better.  Opinions welcome.

You might want to add small description about MVs to Tutorial
documentation "3.2 Views". Here is the current description of views in
the doc.

---------------------------------------------------------------------
3.2. Views

Refer back to the queries in Section 2.6. Suppose the combined listing
of weather records and city location is of particular interest to your
application, but you do not want to type the query each time you need
it. You can create a view over the query, which gives a name to the
query that you can refer to like an ordinary table:

CREATE VIEW myview AS
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather, cities
WHERE city = name;

SELECT * FROM myview;

Making liberal use of views is a key aspect of good SQL database
design. Views allow you to encapsulate the details of the structure of
your tables, which might change as your application evolves, behind
consistent interfaces.

Views can be used in almost any place a real table can be
used. Building views upon other views is not uncommon.
---------------------------------------------------------------------
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese: http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#143Simon Riggs
simon@2ndQuadrant.com
In reply to: Tom Lane (#136)

On 6 March 2013 14:16, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

On 5 March 2013 22:02, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, my opinion is that doing anything like this in the planner is
going to be enormously expensive.

As we already said: no MVs => zero overhead => no problem.

Well, in the first place that statement is false on its face: we'll
still spend cycles looking for relevant MVs, or at least maintaining a
complexly-indexed cache that helps us find out that there are none in
a reasonable amount of time. In the second place, even if it were
approximately true it wouldn't help the people who were using MVs.

We can store info in the relcache, and reduce such a lookup to a
simple if test in the planner. Populating the cache would be easy
enough, approx same overhead as deriving list of constraints for the
relcache.

If you were using MVs, there are further heuristics to apply. MVs come
in various shapes, so we can assess whether they use aggregates,
joins, filters etc and use that for a general match against a query. I
don't see the need for complex assessments in every case.

It costs in
the cases where time savings are possible and not otherwise.

And that is just complete nonsense: matching costs whether you find a
match or not. Could we have a little less Pollyanna-ish optimism and
a bit more realism about the likely cost of such a feature?

It's not a trivial feature; this is a lot of work. But it can be done
efficiently, without significant effect on other workloads. If that
really were to be true, then enable_lookaside = off can be the
default, just as we have for another costly planning feature,
constraint_exclusion.

Matview lookaside is the next-best-action for further work on the
planner, AFAICS. Correctly optimised query parallelism is harder,
IMHO.

What I'm hearing at the moment is "please don't make any changes in my
area" or "don't climb the North face". Considering the rather high bar
to being able to do this effectively, I do understand your interest in
not having your/our time wasted by casual attempts to approach the
problem, but I don't want to slam the door on a serious attempt (2
year project, 1+ man year effort).

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

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#144Kevin Grittner
kgrittn@ymail.com
In reply to: David E. Wheeler (#141)

David E. Wheeler <david@justatheory.com> wrote:

Kevin Grittner <kgrittn@ymail.com> wrote:

I also think that something should be done about the
documentation for indexes.  Right now that always refers to a
"table".  It would clearly be awkward to change that to "table
or materialized view" everywhere.  I wonder if most of thosse
should be changed to "relation" with a few mentions that the
relation could be a table or a materialized view, or whether
some less intrusive change would be better.  Opinions welcome.

Isn’t a materialized view really just a table that gets updated
periodically?

Not exactly.  It is a relation which has some characteristics of a
view (including an entry in pg_rewrite exactly like that for a
view) and some characteristics of a table (including a heap and
optional indexes).  Whether it looks more like a table or more like
a view depends on how you tilt your head.  You could just as easily
say that it is really just a view which periodically caches its
results on disk.  They really are "their own thing".  As future
releases add more subtle "freshness" concepts, incremental updates,
and query rewrite that unique identity will become even more
conspicuous, I think.

And isn’t a non-matierialized view also thought of as a
“relation”?

Yes.  Tables, views, and materialized views are all relations.

If the answer to both those questions is “yes,” I think the term
should remain “table,” with a few mentions that the term includes
materialized views (and excludes foreign tables).

And if the answers are "not exactly" and "yes"?

--
Kevin Grittner
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#145David E. Wheeler
david@justatheory.com
In reply to: Kevin Grittner (#144)

On Mar 7, 2013, at 7:55 AM, Kevin Grittner <kgrittn@ymail.com> wrote:

If the answer to both those questions is “yes,” I think the term
should remain “table,” with a few mentions that the term includes
materialized views (and excludes foreign tables).

And if the answers are "not exactly" and "yes"?

I still tend to think that the term should remain “table,” with brief mentions at the top of pages when the term should be assumed to represent tables and matviews, and otherwise required disambiguations.

Trying to make the least possible work for you here. :-)

David

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#146Nicolas Barbier
nicolas.barbier@gmail.com
In reply to: Kevin Grittner (#132)

2013/3/5 Kevin Grittner <kgrittn@ymail.com>:

Perhaps it would be worth looking for anything in the patch that
you think might be painting us into a corner where it would be hard
to do all the other cool things. While it's late enough in the
process that changing anything like that which you find would be
painful, it might be a lot more painful later if we release without
doing something about it. My hope, of course, is that you won't
find any such thing. With this patch I've tried to provide a
minimal framework onto which these other things can be bolted.
I've tried hard not to do anything which would make it hard to
extend, but new eyes may see something I missed.

(Without having looked at the patch, or even the documentation :-/.)

I think that something that might prove useful is the following:
Keeping in mind the possibility of storing something in the matview’s
heap that doesn’t correspond to what a SELECT * FROM matview would
yield (i.e., the “logical content”). The transformation could be
performed by an INSTEAD rule (similar to how a view is expanded to its
definition, a reference to a matview would expand to its heap content
transformed to the “logical content”).

(Note that I don’t have any reason to believe that the current
implementation would make this more difficult than it should be.)

<ridiculously long rationale for the previous>

(All the following requires making matviews (inter- and intra-)
transactionally up-to-date w.r.t. their base tables at the moment of
querying them. I don’t deal with approximate results, however useful
that might be.)

I think that the possibility of optimizing COUNT(*) (see mail by Greg
Stark in this thread with “the queue of updates with transacion
information that are periodically flattened into the aggregate”) can
be generalized to generic aggregation that way. The idea would be that
a transaction that adds (or deletes or updates) a row in a base table
causes a “delta” row version in the matview. Selecting from the
matview then merges these deltas into one value (for each row that is
logically present in the matview). Every once in a while (or rather
quite often, if the base tables change often), a VACUUM-like clean-up
operations must be run to merge all rows that are “old enough” (i.e.,
whose transactions are not in flight anymore).

Example of trivial aggregation matview weight_per_kind defined as:

SELECT kind, SUM(weight) FROM fruit GROUP BY kind;

The matview would then physically contain rows such as:

xmin, xmax, kind, weight
1000, 0, 'banana', 123
1000, 0, 'apple', 1
1001, 0, 'banana', 2
1002, 0, 'banana', -3

Which means:

* tx 1000 probably performed a clean-up operation and merged a bunch
of banana rows together to yield 123; it also inserted an apple of
weight 1.
* tx 1001 inserted a banana of weight 2. Any clean-up operation coming
by could not merge the 2 into the first row, as long as tx 1000 is in
flight. Otherwise, it would yield 125; physically this would mean
adding a 125 row, marking the 123 and 2 rows as deleted, and then
waiting for VACUUM to remove them).
* tx 1002 deleted a banana with weight 3.

The result of a SELECT * FROM weight_per_kind; would actually execute
SELECT kind, SUM_merge(weight) FROM heap_of_weight_per_kind GROUP BY
kind;

This would result, for tx 1001 (assuming tx 1000 committed and our
snapshot can see it), in:

kind, weight
'banana', 125
'apple', 1

(The -3 is not taken into account, because it is not visible to tx 1001.)

The operator to use at the location of SUM_merge is something that
merges multiple aggregation results (plus results that represent some
kind of “negation”) together. For SUM, it would be SUM itself and the
negation would be numerical negation. There might also be some kind of
“difference” concept used for UPDATE: When updating a weight from 4 to
3, the difference would be -1. Those additional properties could
optionally be added to the definition of each aggregation function; It
must be done for each function that you want to use in such a way.

Other aggregation functions such as AVG would require storing the SUM
+ number of rows in the matview (otherwise two AVGs could not be
merged); again a difference between the heap and the logical content.
Functions such as MIN and MAX are more difficult to fit in this
framework: I can only see how it would work if row deletion were not
allowed (which might still be a valid use-case); luckily, I think MIN
and MAX are not the typical things for which you would want to use
matviews, because quick computation can typically be done directly
using the base tables.

This whole thing would result in incremental updates of
aggregation-matviews that don’t require physical serialization of the
transactions that update the base tables and that query the matview,
which other models (that depend on correspondence between the heap and
logical content of matviews) would probably require.

And that’s where I stop rambling because nobody gets this far anyway,
and I urgently need some sleep :-).

</ridiculously long rationale>

Nicolas

--
A. Because it breaks the logical sequence of discussion.
Q. Why is top posting bad?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#147Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#136)

On Wed, Mar 6, 2013 at 09:16:59AM -0500, Tom Lane wrote:

Simon Riggs <simon@2ndQuadrant.com> writes:

On 5 March 2013 22:02, Tom Lane <tgl@sss.pgh.pa.us> wrote:

FWIW, my opinion is that doing anything like this in the planner is
going to be enormously expensive.

As we already said: no MVs => zero overhead => no problem.

Well, in the first place that statement is false on its face: we'll
still spend cycles looking for relevant MVs, or at least maintaining a
complexly-indexed cache that helps us find out that there are none in
a reasonable amount of time. In the second place, even if it were
approximately true it wouldn't help the people who were using MVs.

It costs in
the cases where time savings are possible and not otherwise.

And that is just complete nonsense: matching costs whether you find a
match or not. Could we have a little less Pollyanna-ish optimism and
a bit more realism about the likely cost of such a feature?

Should we add this to the TODO list as a possibility?

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#148Bruce Momjian
bruce@momjian.us
In reply to: Simon Riggs (#122)

On Tue, Mar 5, 2013 at 08:50:39AM +0000, Simon Riggs wrote:

Its not a different feature, its what most people expect a feature
called MV to deliver. That's not a matter of opinion, its simply how
every other database works currently - Oracle, Teradata, SQLServer at
least. The fact that we don't allow MVs to automatically optimize

Good points.

queries is acceptable, as long as that is clearly marked in some way
to avoid confusion, and I don't mean buried on p5 of the docs. What we
have here is a partial implementation that can be improved upon over
next few releases. I hope anyone isn't going to claim that
"Materialized Views" have been implemented in the release notes in
this release, because unqualified that would be seriously misleading
and might even stifle further funding to improve things to the level
already implemented elsewhere. Just to reiterate, I fully support the
committing of this partial feature into Postgres in this release
because it will be a long haul to complete the full feature and what
we have here is a reasonable stepping stone to get there.

Transactionally up-yo-date MVs can be used like indexes in the
planner. The idea that this is impossible because of the permutations
involved is somewhat ridiculous; there is much published work on
optimising that and some obvious optimisations. Clearly that varies
according to the number of MVs and the number of tables they touch,
not the overall complexity of the query. The overhead is probably same
or less as partial indexes, which we currently think is acceptable. In
any case, if you don't wish that overhead, don't use MVs.

While you are right that automatically using materialized views is like
the optimizer choosing partial indexes, we actually already have
auto-selection of row-level materialized views with expression indexes
and index-only scans. When you do the insert or update, the indexed
function is called and the value stored in the index. If you later
query the function call, we can pull the value right from the index.
This, of course, is a very crude definition of materialized view, but it
seems relevant.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#149Robert Haas
robertmhaas@gmail.com
In reply to: David E. Wheeler (#145)

On Thu, Mar 7, 2013 at 12:14 PM, David E. Wheeler <david@justatheory.com> wrote:

On Mar 7, 2013, at 7:55 AM, Kevin Grittner <kgrittn@ymail.com> wrote:

If the answer to both those questions is “yes,” I think the term
should remain “table,” with a few mentions that the term includes
materialized views (and excludes foreign tables).

And if the answers are "not exactly" and "yes"?

I still tend to think that the term should remain “table,” with brief mentions at the top of pages when the term should be assumed to represent tables and matviews, and otherwise required disambiguations.

This ship has already sailed. There are plenty of places where
operations apply to a subset of the relation types that exist today,
and we either list them out or refer to "relations" generically.
Changing that would require widespread changes to both the
documentation and existing error message text. We cannot decide that
"table" now means "table or materialized view" any more than we can
decide that it means "table or foreign table", as was proposed around
the time those changes went in. Yeah, it's more work, and it's a
little annoying, but it's also clear. Nothing else is.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#150Noah Misch
noah@leadboat.com
In reply to: Noah Misch (#15)

While doing some post-commit review of the 9.3 materialized view feature, I
noticed a few loose ends:

On Thu, Jan 24, 2013 at 01:09:28PM -0500, Noah Misch wrote:

Note that [...] "ALTER TABLE ... RENAME CONSTRAINT" [is]
currently supported for MVs by ALTER TABLE but not by ALTER MATERIALIZED VIEW.

There's no documented support for table constraints on MVs, but UNIQUE
constraints are permitted:

[local] test=# alter materialized view mymv add unique (c);
ALTER MATERIALIZED VIEW
[local] test=# alter materialized view mymv add check (c > 0);
ERROR: "mymv" is not a table
[local] test=# alter materialized view mymv add primary key (c);
ERROR: "mymv" is not a table or foreign table

The above points still apply.

Also, could you explain the use of RelationCacheInvalidateEntry() in
ExecRefreshMatView()? CacheInvalidateRelcacheByRelid() followed by
CommandCounterIncrement() is the typical pattern; this is novel. I suspect,
though, neither is necessary now that the relcache does not maintain populated
status based on a fork size reading.

Thanks,
nm

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#151Kevin Grittner
kgrittn@ymail.com
In reply to: Noah Misch (#150)

Apologies, but this sub-thread got lost when I changed email
accounts.  I found it in a final review to make sure nothing had
fallen through the cracks.

Noah Misch <noah@leadboat.com> wrote:

On Thu, Jan 24, 2013 at 01:09:28PM -0500, Noah Misch wrote:

There's no documented support for table constraints on MVs, but
UNIQUE constraints are permitted:

[local] test=# alter materialized view mymv add unique (c);
ALTER MATERIALIZED VIEW

Fix pushed.

Also, could you explain the use of RelationCacheInvalidateEntry()
in ExecRefreshMatView()?  CacheInvalidateRelcacheByRelid()
followed by CommandCounterIncrement() is the typical pattern;
this is novel. I suspect, though, neither is necessary now that
the relcache does not maintain populated status based on a fork
size reading.

Yeah, that was part of the attempt to support unlogged materialized
views while also not returning bogus results if the view had not
been populated, using heap file size.  I agree that this line can
just come out.  If there are no objections real soon now, I will
remove it in master and the 9.3 branch before the release
candidate.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#152Kevin Grittner
kgrittn@ymail.com
In reply to: Kevin Grittner (#151)

Kevin Grittner <kgrittn@ymail.com> wrote:

Noah Misch <noah@leadboat.com> wrote:

Also, could you explain the use of RelationCacheInvalidateEntry()
in ExecRefreshMatView()?  CacheInvalidateRelcacheByRelid()
followed by CommandCounterIncrement() is the typical pattern;
this is novel. I suspect, though, neither is necessary now that
the relcache does not maintain populated status based on a fork
size reading.

Yeah, that was part of the attempt to support unlogged materialized
views while also not returning bogus results if the view had not
been populated, using heap file size.  I agree that this line can
just come out.  If there are no objections real soon now, I will
remove it in master and the 9.3 branch before the release
candidate.

Done.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers