Materialized views WIP patch
Attached is a patch that is still WIP but that I think is getting
pretty close to completion. It is not intended to be the be-all and
end-all for materialized views, but the minimum useful feature set --
which is all that I've had time to do for this release. In
particular, the view is only updated on demand by a complete rebuild.
For the next release, I hope to build on this base to allow more
eager and incremental updates, and perhaps a concurrent batch update.
1. CREATE MATERIALIZED VIEW syntax is stolen directly from CREATE
TABLE AS, with all the same clauses supported. That includes
declaring a materialized view to be temporary or unlogged.
2. MVs don't support inheritance.
3. MVs can't define foreign keys.
4. MVs can't be the target of foreign keys.
5. MVs can't have triggers.
6. Users can't create rules which reference MVs (although MVs
[ab]use the rules mechanism internally, similar to how views do).
7. MVs can't be converted to views, nor vice versa.
8. Users may not directly use INSERT/UPDATE/DELETE on an MV.
9. MVs can't directly be used in a COPY statement, but can be the
source of data using a SELECT.
10. MVs can't own sequences.
11. MVs can't be the target of LOCK statements, although other
statements get locks just like a table.
12. MVs can't use data modifying CTEs in their definitions.
13. pg_class now has a relisvalid column, which is true if an MV is
truncated or created WITH NO DATA. You can not scan a relation
flagged as invalid.
14. ALTER MATERIALIZED VIEW is supported for the options that seemed
to make sense. For example, you can change the tablespace or
schema, but you cannot add or drop column with ALTER.
15. The SELECT query used to define the MV may not contain a
data-modifying CTE.
16. To get new data into the MV, the command is LOAD MATERIALIZED
VIEW mat view_name. This seemed more descriptive to me that the
alternatives and avoids declaring any new keywords beyond
MATERIALIZED. If the MV is flagged as relisvalid == false, this
will change it to true.
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction, it didn't seem to make any sense to try to
apply SERIALIZABLE transaction semantics to queries looking at
the contents of an MV, although if LMV is run in a SERIALIZABLE
transaction the MV data is guaranteed to be free of serialization
anomalies. This does leave the transaction running the LOAD
command vulnerable to serialization failures unless it is also
READ ONLY DEFERRABLE.
18. Bound parameters are not supported for the CREATE MATERIALIZED
VIEW statement.
19. LMV doesn't show a row count. It wouldn't be hard to add, it just
seemed a little out of place to do that, when CLUSTER, etc.,
don't.
I wasn't able to wrap up a few things in time for this commitfest:
- Documentation is incomplete.
- pg_dump support needs addtional dependencies added to properly
handle MVs which are defined using other MVs.
- pg_dump binary hasn't had a lot of attention yet.
- There are no regression tests yet.
- I ran into problems getting the validity check working right, so I
have disabled it by commenting out the function body in this
patch.
- TRUNCATE should probably support a MATERIALIZED VIEW clause.
It would be good to have some discussion to try to reach a consensus
about whether we need to differentiate between *missing* datat (where
a materialized view which has been loaded WITH NO DATA or TRUNCATEd
and has not been subsequently LOADed) and potentially *stale* data.
If we don't care to distinguish between a view which generated no
rows when it ran and a one for which the query has not been run, we
can avoid adding the relisvalid flag, and we could support UNLOGGED
MVs. Perhaps someone can come up with a better solution to that
problem.
In the long term, we will probably need to separate the
implementation of CREATE TABLE AS and CREATE MATERIALIZED VIEW, but
for now there is so little that they need to do differently it seemed
less evil to have a few "if" clauses that that much duplicated code.
The paint is pretty wet still, but hopefully people can evaluate the
approach and work out any issues with the design choices in the CF so
that it can be wrapped up nicely for the next one.
92 files changed, 2377 insertions(+), 440 deletions(-)
-Kevin
Attachments:
matview-v1.patchtext/x-patch; charset=utf-8; name=matview-v1.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
***************
*** 275,281 **** get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
"CREATE TEMPORARY TABLE info_rels (reloid) AS SELECT c.oid "
"FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
! "WHERE relkind IN ('r', 'i'%s) AND "
/* exclude possible orphaned temp tables */
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
--- 275,281 ----
"CREATE TEMPORARY TABLE info_rels (reloid) AS SELECT c.oid "
"FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
! "WHERE relkind IN ('r', 'm', 'i'%s) AND "
/* exclude possible orphaned temp tables */
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
*** a/contrib/pg_upgrade/pg_upgrade.c
--- b/contrib/pg_upgrade/pg_upgrade.c
***************
*** 425,432 **** set_frozenxids(void)
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
! /* only heap and TOAST are vacuumed */
! "WHERE relkind IN ('r', 't')",
old_cluster.controldata.chkpnt_nxtxid));
PQfinish(conn);
--- 425,432 ----
PQclear(executeQueryOrDie(conn,
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
! /* only heap, materialized view, and TOAST are vacuumed */
! "WHERE relkind IN ('r', 'm', 't')",
old_cluster.controldata.chkpnt_nxtxid));
PQfinish(conn);
*** a/contrib/pg_upgrade/version_old_8_3.c
--- b/contrib/pg_upgrade/version_old_8_3.c
***************
*** 145,151 **** old_8_3_check_for_tsquery_usage(ClusterInfo *cluster)
"FROM pg_catalog.pg_class c, "
" pg_catalog.pg_namespace n, "
" pg_catalog.pg_attribute a "
! "WHERE c.relkind = 'r' AND "
" c.oid = a.attrelid AND "
" NOT a.attisdropped AND "
" a.atttypid = 'pg_catalog.tsquery'::pg_catalog.regtype AND "
--- 145,151 ----
"FROM pg_catalog.pg_class c, "
" pg_catalog.pg_namespace n, "
" pg_catalog.pg_attribute a "
! "WHERE c.relkind in ('r', 'm') AND "
" c.oid = a.attrelid AND "
" NOT a.attisdropped AND "
" a.atttypid = 'pg_catalog.tsquery'::pg_catalog.regtype AND "
***************
*** 323,329 **** old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode)
"FROM pg_catalog.pg_class c, "
" pg_catalog.pg_namespace n, "
" pg_catalog.pg_attribute a "
! "WHERE c.relkind = 'r' AND "
" c.oid = a.attrelid AND "
" NOT a.attisdropped AND "
" a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND "
--- 323,329 ----
"FROM pg_catalog.pg_class c, "
" pg_catalog.pg_namespace n, "
" pg_catalog.pg_attribute a "
! "WHERE c.relkind in ('r', 'm') AND "
" c.oid = a.attrelid AND "
" NOT a.attisdropped AND "
" a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND "
***************
*** 343,349 **** old_8_3_rebuild_tsvector_tables(ClusterInfo *cluster, bool check_mode)
"FROM pg_catalog.pg_class c, " \
" pg_catalog.pg_namespace n, " \
" pg_catalog.pg_attribute a " \
! "WHERE c.relkind = 'r' AND " \
" c.oid = a.attrelid AND " \
" NOT a.attisdropped AND " \
" a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND " \
--- 343,349 ----
"FROM pg_catalog.pg_class c, " \
" pg_catalog.pg_namespace n, " \
" pg_catalog.pg_attribute a " \
! "WHERE c.relkind in ('r', 'm') AND " \
" c.oid = a.attrelid AND " \
" NOT a.attisdropped AND " \
" a.atttypid = 'pg_catalog.tsvector'::pg_catalog.regtype AND " \
*** a/contrib/pgstattuple/pgstattuple.c
--- b/contrib/pgstattuple/pgstattuple.c
***************
*** 216,221 **** pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
--- 216,222 ----
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
case RELKIND_TOASTVALUE:
case RELKIND_SEQUENCE:
return pgstat_heap(rel, fcinfo);
*** a/contrib/sepgsql/dml.c
--- b/contrib/sepgsql/dml.c
***************
*** 191,196 **** check_relation_privileges(Oid relOid,
--- 191,197 ----
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
result = sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_TABLE,
required,
***************
*** 226,232 **** check_relation_privileges(Oid relOid,
/*
* Only columns owned by relations shall be checked
*/
! if (relkind != RELKIND_RELATION)
return true;
/*
--- 227,233 ----
/*
* Only columns owned by relations shall be checked
*/
! if (relkind != RELKIND_RELATION && relkind != RELKIND_MATVIEW)
return true;
/*
*** a/contrib/sepgsql/label.c
--- b/contrib/sepgsql/label.c
***************
*** 764,769 **** exec_object_restorecon(struct selabel_handle * sehnd, Oid catalogId)
--- 764,771 ----
objtype = SELABEL_DB_SEQUENCE;
else if (relForm->relkind == RELKIND_VIEW)
objtype = SELABEL_DB_VIEW;
+ else if (relForm->relkind == RELKIND_MATVIEW)
+ objtype = SELABEL_DB_TABLE;
else
continue; /* no need to assign security label */
***************
*** 782,788 **** exec_object_restorecon(struct selabel_handle * sehnd, Oid catalogId)
case AttributeRelationId:
attForm = (Form_pg_attribute) GETSTRUCT(tuple);
! if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION)
continue; /* no need to assign security label */
objtype = SELABEL_DB_COLUMN;
--- 784,791 ----
case AttributeRelationId:
attForm = (Form_pg_attribute) GETSTRUCT(tuple);
! if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION &&
! get_rel_relkind(attForm->attrelid) != RELKIND_MATVIEW)
continue; /* no need to assign security label */
objtype = SELABEL_DB_COLUMN;
*** a/contrib/sepgsql/relation.c
--- b/contrib/sepgsql/relation.c
***************
*** 54,61 **** sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
Form_pg_attribute attForm;
/*
! * Only attributes within regular relation have individual security
! * labels.
*/
if (get_rel_relkind(relOid) != RELKIND_RELATION)
return;
--- 54,61 ----
Form_pg_attribute attForm;
/*
! * Only attributes within regular relation can have ALTER to add columns
! * with individual security labels.
*/
if (get_rel_relkind(relOid) != RELKIND_RELATION)
return;
***************
*** 159,165 **** sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
ObjectAddress object;
char *audit_name;
! if (get_rel_relkind(relOid) != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security label on non-regular columns")));
--- 159,166 ----
ObjectAddress object;
char *audit_name;
! if (get_rel_relkind(relOid) != RELKIND_RELATION &&
! get_rel_relkind(relOid) != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security label on non-regular columns")));
***************
*** 252,257 **** sepgsql_relation_post_create(Oid relOid)
--- 253,259 ----
switch (classForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
tclass = SEPG_CLASS_DB_TABLE;
tclass_text = "table";
break;
***************
*** 301,310 **** sepgsql_relation_post_create(Oid relOid)
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
/*
! * We also assigns a default security label on columns of the new regular
! * tables.
*/
! if (classForm->relkind == RELKIND_RELATION)
{
Relation arel;
ScanKeyData akey;
--- 303,313 ----
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
/*
! * We also assign a default security label on columns of new regular
! * tables and materialized views.
*/
! if (classForm->relkind == RELKIND_RELATION ||
! classForm->relkind == RELKIND_MATVIEW)
{
Relation arel;
ScanKeyData akey;
***************
*** 378,383 **** sepgsql_relation_drop(Oid relOid)
--- 381,387 ----
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
tclass = SEPG_CLASS_DB_TABLE;
break;
case RELKIND_SEQUENCE:
***************
*** 489,499 **** sepgsql_relation_relabel(Oid relOid, const char *seclabel)
tclass = SEPG_CLASS_DB_SEQUENCE;
else if (relkind == RELKIND_VIEW)
tclass = SEPG_CLASS_DB_VIEW;
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security labels on relations except "
! "for tables, sequences or views")));
object.classId = RelationRelationId;
object.objectId = relOid;
--- 493,505 ----
tclass = SEPG_CLASS_DB_SEQUENCE;
else if (relkind == RELKIND_VIEW)
tclass = SEPG_CLASS_DB_VIEW;
+ else if (relkind == RELKIND_MATVIEW)
+ tclass = SEPG_CLASS_DB_TABLE;
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security labels on relations except "
! "for tables, sequences, views, or materialized views")));
object.classId = RelationRelationId;
object.objectId = relOid;
***************
*** 536,541 **** sepgsql_relation_setattr(Oid relOid)
--- 542,548 ----
switch (get_rel_relkind(relOid))
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
tclass = SEPG_CLASS_DB_TABLE;
break;
case RELKIND_SEQUENCE:
*** a/contrib/sepgsql/sepgsql.h
--- b/contrib/sepgsql/sepgsql.h
***************
*** 32,37 ****
--- 32,39 ----
/*
* Internally used code of object classes
+ *
+ * NOTE: Materialized views are treated as tables for now.
*/
#define SEPG_CLASS_PROCESS 0
#define SEPG_CLASS_FILE 1
*** a/contrib/vacuumlo/vacuumlo.c
--- b/contrib/vacuumlo/vacuumlo.c
***************
*** 209,215 **** vacuumlo(const char *database, const struct _param * param)
strcat(buf, " AND a.atttypid = t.oid ");
strcat(buf, " AND c.relnamespace = s.oid ");
strcat(buf, " AND t.typname in ('oid', 'lo') ");
! strcat(buf, " AND c.relkind = 'r'");
strcat(buf, " AND s.nspname !~ '^pg_'");
res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
--- 209,215 ----
strcat(buf, " AND a.atttypid = t.oid ");
strcat(buf, " AND c.relnamespace = s.oid ");
strcat(buf, " AND t.typname in ('oid', 'lo') ");
! strcat(buf, " AND c.relkind in ('r', 'm')");
strcat(buf, " AND s.nspname !~ '^pg_'");
res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1555,1562 ****
The catalog <structname>pg_class</structname> catalogs tables and most
everything else that has columns or is otherwise similar to a
table. This includes indexes (but see also
! <structname>pg_index</structname>), sequences, views, composite types,
! and TOAST tables; see <structfield>relkind</>.
Below, when we mean all of these
kinds of objects we speak of <quote>relations</quote>. Not all
columns are meaningful for all relation types.
--- 1555,1562 ----
The catalog <structname>pg_class</structname> catalogs tables and most
everything else that has columns or is otherwise similar to a
table. This includes indexes (but see also
! <structname>pg_index</structname>), sequences, views, materialized
! views, composite types, and TOAST tables; see <structfield>relkind</>.
Below, when we mean all of these
kinds of objects we speak of <quote>relations</quote>. Not all
columns are meaningful for all relation types.
***************
*** 1740,1747 ****
<entry></entry>
<entry>
<literal>r</> = ordinary table, <literal>i</> = index,
! <literal>S</> = sequence, <literal>v</> = view, <literal>c</> =
! composite type, <literal>t</> = TOAST table,
<literal>f</> = foreign table
</entry>
</row>
--- 1740,1748 ----
<entry></entry>
<entry>
<literal>r</> = ordinary table, <literal>i</> = index,
! <literal>S</> = sequence, <literal>v</> = view,
! <literal>m</> = materialized view,
! <literal>c</> = composite type, <literal>t</> = TOAST table,
<literal>f</> = foreign table
</entry>
</row>
***************
*** 1814,1819 ****
--- 1815,1830 ----
</row>
<row>
+ <entry><structfield>relisvalid</structfield></entry>
+ <entry><type>boolean</type></entry>
+ <entry></entry>
+ <entry>
+ Relation is valid and can be scanned. Currently, this is used only for
+ materialized views; for other relation types, it will always be true.
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 21,26 **** Complete list of usable sgml source files in this directory.
--- 21,27 ----
<!ENTITY alterIndex SYSTEM "alter_index.sgml">
<!ENTITY alterLanguage SYSTEM "alter_language.sgml">
<!ENTITY alterLargeObject SYSTEM "alter_large_object.sgml">
+ <!ENTITY alterMaterializedView SYSTEM "alter_materialized_view.sgml">
<!ENTITY alterOperator SYSTEM "alter_operator.sgml">
<!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
<!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
***************
*** 62,67 **** Complete list of usable sgml source files in this directory.
--- 63,69 ----
<!ENTITY createGroup SYSTEM "create_group.sgml">
<!ENTITY createIndex SYSTEM "create_index.sgml">
<!ENTITY createLanguage SYSTEM "create_language.sgml">
+ <!ENTITY createMaterializedView SYSTEM "create_materialized_view.sgml">
<!ENTITY createOperator SYSTEM "create_operator.sgml">
<!ENTITY createOperatorClass SYSTEM "create_opclass.sgml">
<!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
***************
*** 101,106 **** Complete list of usable sgml source files in this directory.
--- 103,109 ----
<!ENTITY dropGroup SYSTEM "drop_group.sgml">
<!ENTITY dropIndex SYSTEM "drop_index.sgml">
<!ENTITY dropLanguage SYSTEM "drop_language.sgml">
+ <!ENTITY dropMaterializedView SYSTEM "drop_materialized_view.sgml">
<!ENTITY dropOperator SYSTEM "drop_operator.sgml">
<!ENTITY dropOperatorClass SYSTEM "drop_opclass.sgml">
<!ENTITY dropOperatorFamily SYSTEM "drop_opfamily.sgml">
***************
*** 129,134 **** Complete list of usable sgml source files in this directory.
--- 132,138 ----
<!ENTITY insert SYSTEM "insert.sgml">
<!ENTITY listen SYSTEM "listen.sgml">
<!ENTITY load SYSTEM "load.sgml">
+ <!ENTITY loadMaterializedView SYSTEM "load_materialized_view.sgml">
<!ENTITY lock SYSTEM "lock.sgml">
<!ENTITY move SYSTEM "move.sgml">
<!ENTITY notify SYSTEM "notify.sgml">
*** a/doc/src/sgml/ref/alter_extension.sgml
--- b/doc/src/sgml/ref/alter_extension.sgml
***************
*** 39,44 **** ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea
--- 39,45 ----
FOREIGN DATA WRAPPER <replaceable class="PARAMETER">object_name</replaceable> |
FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> |
FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
+ MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
OPERATOR <replaceable class="PARAMETER">operator_name</replaceable> (<replaceable class="PARAMETER">left_type</replaceable>, <replaceable class="PARAMETER">right_type</replaceable>) |
OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
OPERATOR FAMILY <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
*** /dev/null
--- b/doc/src/sgml/ref/alter_materialized_view.sgml
***************
*** 0 ****
--- 1,152 ----
+ <!--
+ doc/src/sgml/ref/alter_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-ALTERMATERIALIZEDVIEW">
+ <refmeta>
+ <refentrytitle>ALTER MATERIALIZED VIEW</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ALTER MATERIALIZED VIEW</refname>
+ <refpurpose>change the definition of a materialized view</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterview">
+ <primary>ALTER MATERIALIZED VIEW</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">view_option_name</replaceable> [= <replaceable class="parameter">view_option_value</replaceable>] [, ... ] )
+ ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">view_option_name</replaceable> [, ... ] )
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>ALTER MATERIALIZED VIEW</command> changes various auxiliary
+ properties of a materialized view.
+ </para>
+
+ <para>
+ You must own the materialized view to use <command>ALTER MATERIALIZED
+ VIEW</>. To change a materailized view's schema, you must also have
+ <literal>CREATE</> privilege on the new schema.
+ To alter the owner, you must also be a direct or indirect member of the new
+ owning role, and that role must have <literal>CREATE</literal> privilege on
+ the materialized view's schema. (These restrictions enforce that altering
+ the owner doesn't do anything you couldn't do by dropping and recreating the
+ materialized view. However, a superuser can alter ownership of any view
+ anyway.)
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of an existing materialized view.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>IF EXISTS</literal></term>
+ <listitem>
+ <para>
+ Do not throw an error if the materialized view does not exist. A notice
+ is issued in this case.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">new_owner</replaceable></term>
+ <listitem>
+ <para>
+ The user name of the new owner of the materialized view.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_name</replaceable></term>
+ <listitem>
+ <para>
+ The new name for the materialized view.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_schema</replaceable></term>
+ <listitem>
+ <para>
+ The new schema for the materialized view.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">view_option_name</replaceable></term>
+ <listitem>
+ <para>
+ The name of a materialized view option to be set or reset.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">view_option_name</replaceable></term>
+ <listitem>
+ <para>
+ The new value for a view option.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ To rename the materialized view <literal>foo</literal> to
+ <literal>bar</literal>:
+ <programlisting>
+ ALTER MATERIALIZED VIEW foo RENAME TO bar;
+ </programlisting></para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <command>ALTER MATERIALIZED VIEW</command> is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-creatematerializedview"></member>
+ <member><xref linkend="sql-dropmaterializedview"></member>
+ <member><xref linkend="sql-loadmaterializedview"></member>
+ </simplelist>
+ </refsect1>
+ </refentry>
*** a/doc/src/sgml/ref/comment.sgml
--- b/doc/src/sgml/ref/comment.sgml
***************
*** 38,43 **** COMMENT ON
--- 38,44 ----
FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
INDEX <replaceable class="PARAMETER">object_name</replaceable> |
LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> |
+ MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
OPERATOR <replaceable class="PARAMETER">operator_name</replaceable> (<replaceable class="PARAMETER">left_type</replaceable>, <replaceable class="PARAMETER">right_type</replaceable>) |
OPERATOR CLASS <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
OPERATOR FAMILY <replaceable class="PARAMETER">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
***************
*** 279,284 **** COMMENT ON FUNCTION my_function (timestamp) IS 'Returns Roman Numeral';
--- 280,286 ----
COMMENT ON INDEX my_index IS 'Enforces uniqueness on employee ID';
COMMENT ON LANGUAGE plpython IS 'Python support for stored procedures';
COMMENT ON LARGE OBJECT 346344 IS 'Planning document';
+ COMMENT ON MATERIALIZED VIEW my_matview IS 'Summary of order history';
COMMENT ON OPERATOR ^ (text, text) IS 'Performs intersection of two texts';
COMMENT ON OPERATOR - (NONE, integer) IS 'Unary minus';
COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees';
*** /dev/null
--- b/doc/src/sgml/ref/create_materialized_view.sgml
***************
*** 0 ****
--- 1,244 ----
+ <!--
+ doc/src/sgml/ref/create_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-CREATEMATERIALIZEDVIEW">
+ <refmeta>
+ <refentrytitle>CREATE MATERIALIZED VIEW</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE MATERIALIZED VIEW</refname>
+ <refpurpose>define a new materialized view</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-creatematerializedview">
+ <primary>CREATE MATERIALIZED VIEW</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] MATERIALIZED VIEW <replaceable>table_name</replaceable>
+ [ (<replaceable>column_name</replaceable> [, ...] ) ]
+ [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+ [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+ [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+ AS <replaceable>query</replaceable>
+ [ WITH [ NO ] DATA ]
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>CREATE MATERIALIZED VIEW</command> defines a materialized view of
+ a query. The query is executed and used to populate the view at the time
+ the command is issued (unless <command>WITH NO DATA</> is used) and may be
+ refreshed later using <command>LOAD MATERIALIZED VIEW</command>.
+ </para>
+
+ <para>
+ <command>CREATE MATERIALIZED VIEW</command> is similar to
+ <command>CREATE TABLE AS</>, except that it also remembers the query used
+ to initialize the view, so that it can be refreshed later upon demand.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>GLOBAL</literal> or <literal>LOCAL</literal></term>
+ <listitem>
+ <para>
+ Ignored for compatibility. Use of these keywords is deprecated;
+ refer to <xref linkend="sql-createtable"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>TEMPORARY</> or <literal>TEMP</></term>
+ <listitem>
+ <para>
+ If specified, the materialized view will be temporary.
+ Refer to <xref linkend="sql-createtable"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>UNLOGGED</></term>
+ <listitem>
+ <para>
+ If specified, the materialized view will be unlogged.
+ Refer to <xref linkend="sql-createtable"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>table_name</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the materialized view to be
+ created.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>column_name</replaceable></term>
+ <listitem>
+ <para>
+ The name of a column in the new materialized view. If column names are
+ not provided, they are taken from the output column names of the query.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )</literal></term>
+ <listitem>
+ <para>
+ This clause specifies optional storage parameters for the new
+ materialized view; see <xref linkend="sql-createtable-storage-parameters"
+ endterm="sql-createtable-storage-parameters-title"> for more
+ information. The <literal>WITH</> clause
+ can also include <literal>OIDS=TRUE</> (or just <literal>OIDS</>)
+ to specify that rows of the new table
+ should have OIDs (object identifiers) assigned to them, or
+ <literal>OIDS=FALSE</> to specify that the rows should not have OIDs.
+ See <xref linkend="sql-createtable"> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>WITH OIDS</></term>
+ <term><literal>WITHOUT OIDS</></term>
+ <listitem>
+ <para>
+ These are obsolescent syntaxes equivalent to <literal>WITH (OIDS)</>
+ and <literal>WITH (OIDS=FALSE)</>, respectively. If you wish to give
+ both an <literal>OIDS</> setting and storage parameters, you must use
+ the <literal>WITH ( ... )</> syntax; see above.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>ON COMMIT</literal></term>
+ <listitem>
+ <para>
+ The behavior of temporary materialized views at the end of a transaction
+ block can be controlled using <literal>ON COMMIT</literal>.
+ The three options are:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>PRESERVE ROWS</literal></term>
+ <listitem>
+ <para>
+ No special action is taken at the ends of transactions.
+ This is the default behavior.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DELETE ROWS</literal></term>
+ <listitem>
+ <para>
+ All rows in the temporary materialized view will be deleted at the
+ end of each transaction block. Essentially, an automatic <xref
+ linkend="sql-truncate"> is done
+ at each commit.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP</literal></term>
+ <listitem>
+ <para>
+ The temporary materialized view will be dropped at the end of the
+ current transaction block.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable></literal></term>
+ <listitem>
+ <para>
+ The <replaceable class="PARAMETER">tablespace_name</replaceable> is the name
+ of the tablespace in which the new materialized view is to be created.
+ If not specified,
+ <xref linkend="guc-default-tablespace"> is consulted, or
+ <xref linkend="guc-temp-tablespaces"> if the materialized view is
+ temporary.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable>query</replaceable></term>
+ <listitem>
+ <para>
+ A <xref linkend="sql-select">, <link
+ linkend="sql-table">TABLE</link>, or <xref linkend="sql-values">
+ command, or an <xref linkend="sql-execute"> command that runs a
+ prepared <command>SELECT</>, <command>TABLE</>, or
+ <command>VALUES</> query.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>WITH [ NO ] DATA</></term>
+ <listitem>
+ <para>
+ This clause specifies whether or not the materialized view should be
+ populated at creation time. If not, the materialized view will be
+ flagged as invalid and cannot be queried until <command>LOAD
+ MATERIALIZED VIEW</> is used.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <command>CREATE MATERIALIZED VIEW</command> is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-altermaterializedview"></member>
+ <member><xref linkend="sql-createtableas"></member>
+ <member><xref linkend="sql-createview"></member>
+ <member><xref linkend="sql-dropmaterializedview"></member>
+ <member><xref linkend="sql-loadmaterializedview"></member>
+ </simplelist>
+ </refsect1>
+
+ </refentry>
*** a/doc/src/sgml/ref/create_table_as.sgml
--- b/doc/src/sgml/ref/create_table_as.sgml
***************
*** 340,345 **** CREATE TEMP TABLE films_recent WITH (OIDS) ON COMMIT DROP AS
--- 340,346 ----
<title>See Also</title>
<simplelist type="inline">
+ <member><xref linkend="sql-creatematerializedview"></member>
<member><xref linkend="sql-createtable"></member>
<member><xref linkend="sql-execute"></member>
<member><xref linkend="sql-select"></member>
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
***************
*** 256,261 **** CREATE VIEW <replaceable class="parameter">name</replaceable> [ ( <replaceable c
--- 256,262 ----
<title>See Also</title>
<simplelist type="inline">
+ <member><xref linkend="sql-creatematerializedview"></member>
<member><xref linkend="sql-alterview"></member>
<member><xref linkend="sql-dropview"></member>
</simplelist>
*** /dev/null
--- b/doc/src/sgml/ref/drop_materialized_view.sgml
***************
*** 0 ****
--- 1,114 ----
+ <!--
+ doc/src/sgml/ref/drop_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-DROPMATERIALIZEDVIEW">
+ <refmeta>
+ <refentrytitle>DROP MATERIALIZED VIEW</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>DROP MATERIALIZED VIEW</refname>
+ <refpurpose>remove a materialized view</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropmaterializedview">
+ <primary>DROP MATERIALIZED VIEW</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ DROP MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>DROP MATERIALIZED VIEW</command> drops an existing materialized
+ view. To execute this command you must be the owner of the materialized
+ view.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>IF EXISTS</literal></term>
+ <listitem>
+ <para>
+ Do not throw an error if the materialized view does not exist. A notice
+ is issued in this case.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="PARAMETER">name</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the materialized view to
+ remove.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>CASCADE</literal></term>
+ <listitem>
+ <para>
+ Automatically drop objects that depend on the materialized view (such as
+ other materialized views, or regular views).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Refuse to drop the materialized view if any objects depend on it. This
+ is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ This command will remove the materialized view called
+ <literal>order_summary</literal>:
+ <programlisting>
+ DROP MATERIALIZED VIEW order_summary;
+ </programlisting></para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <command>DROP MATERIALIZED VIEW</command> is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-creatematerializedview"></member>
+ <member><xref linkend="sql-altermaterializedview"></member>
+ <member><xref linkend="sql-loadmaterializedview"></member>
+ </simplelist>
+ </refsect1>
+
+ </refentry>
*** /dev/null
--- b/doc/src/sgml/ref/load_materialized_view.sgml
***************
*** 0 ****
--- 1,83 ----
+ <!--
+ doc/src/sgml/ref/load_materialized_view.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-LOADMATERIALIZEDVIEW">
+ <refmeta>
+ <refentrytitle>LOAD MATERIALIZED VIEW</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>LOAD MATERIALIZED VIEW</refname>
+ <refpurpose>refresh a materialized view</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-dropmaterializedview">
+ <primary>LOAD MATERIALIZED VIEW</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ LOAD MATERIALIZED VIEW <replaceable class="PARAMETER">name</replaceable>
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>LOAD MATERIALIZED VIEW</command> refreshes the contents of a
+ materialized view by re-executing the backing query.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">name</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the materialized view to
+ refresh.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ This command will refresh the materialized view called
+ <literal>order_summary</literal>:
+ <programlisting>
+ LOAD MATERIALIZED VIEW order_summary;
+ </programlisting></para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <command>LOAD MATERIALIZED VIEW</command> is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-creatematerializedview"></member>
+ <member><xref linkend="sql-altermaterializedview"></member>
+ <member><xref linkend="sql-dropmaterializedview"></member>
+ </simplelist>
+ </refsect1>
+
+ </refentry>
*** a/doc/src/sgml/ref/security_label.sgml
--- b/doc/src/sgml/ref/security_label.sgml
***************
*** 32,37 **** SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON
--- 32,38 ----
FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable>
FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) |
LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> |
+ MATERIALIZED VIEW <replaceable class="PARAMETER">object_name</replaceable>
[ PROCEDURAL ] LANGUAGE <replaceable class="PARAMETER">object_name</replaceable> |
ROLE <replaceable class="PARAMETER">object_name</replaceable> |
SCHEMA <replaceable class="PARAMETER">object_name</replaceable> |
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 49,54 ****
--- 49,55 ----
&alterIndex;
&alterLanguage;
&alterLargeObject;
+ &alterMaterializedView;
&alterOperator;
&alterOperatorClass;
&alterOperatorFamily;
***************
*** 90,95 ****
--- 91,97 ----
&createGroup;
&createIndex;
&createLanguage;
+ &createMaterializedView;
&createOperator;
&createOperatorClass;
&createOperatorFamily;
***************
*** 129,134 ****
--- 131,137 ----
&dropGroup;
&dropIndex;
&dropLanguage;
+ &dropMaterializedView;
&dropOperator;
&dropOperatorClass;
&dropOperatorFamily;
***************
*** 157,162 ****
--- 160,166 ----
&insert;
&listen;
&load;
+ &loadMaterializedView;
&lock;
&move;
¬ify;
*** a/src/backend/access/common/reloptions.c
--- b/src/backend/access/common/reloptions.c
***************
*** 791,796 **** extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
--- 791,797 ----
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_INDEX:
***************
*** 1191,1196 **** heap_reloptions(char relkind, Datum reloptions, bool validate)
--- 1192,1198 ----
}
return (bytea *) rdopts;
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
case RELKIND_VIEW:
return default_reloptions(reloptions, validate, RELOPT_KIND_VIEW);
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
***************
*** 2087,2093 **** heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
* If the new tuple is too big for storage or contains already toasted
* out-of-line attributes from some other relation, invoke the toaster.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(tup));
--- 2087,2094 ----
* If the new tuple is too big for storage or contains already toasted
* out-of-line attributes from some other relation, invoke the toaster.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION &&
! relation->rd_rel->relkind != RELKIND_MATVIEW)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(tup));
***************
*** 2628,2634 **** l1:
* because we need to look at the contents of the tuple, but it's OK to
* release the content lock on the buffer first.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&tp));
--- 2629,2636 ----
* because we need to look at the contents of the tuple, but it's OK to
* release the content lock on the buffer first.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION &&
! relation->rd_rel->relkind != RELKIND_MATVIEW)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&tp));
***************
*** 2987,2993 **** l2:
* We need to invoke the toaster if there are already any out-of-line
* toasted values present, or if the new tuple is over-threshold.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&oldtup));
--- 2989,2996 ----
* We need to invoke the toaster if there are already any out-of-line
* toasted values present, or if the new tuple is over-threshold.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION &&
! relation->rd_rel->relkind != RELKIND_MATVIEW)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&oldtup));
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
***************
*** 353,362 **** toast_delete(Relation rel, HeapTuple oldtup)
bool toast_isnull[MaxHeapAttributeNumber];
/*
! * We should only ever be called for tuples of plain relations ---
! * recursing on a toast rel is bad news.
*/
! Assert(rel->rd_rel->relkind == RELKIND_RELATION);
/*
* Get the tuple descriptor and break down the tuple into fields.
--- 353,363 ----
bool toast_isnull[MaxHeapAttributeNumber];
/*
! * We should only ever be called for tuples of plain relations or
! * materialized views --- recursing on a toast rel is bad news.
*/
! Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
! rel->rd_rel->relkind == RELKIND_MATVIEW);
/*
* Get the tuple descriptor and break down the tuple into fields.
***************
*** 443,449 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
* We should only ever be called for tuples of plain relations ---
* recursing on a toast rel is bad news.
*/
! Assert(rel->rd_rel->relkind == RELKIND_RELATION);
/*
* Get the tuple descriptor and break down the tuple(s) into fields.
--- 444,451 ----
* We should only ever be called for tuples of plain relations ---
* recursing on a toast rel is bad news.
*/
! Assert(rel->rd_rel->relkind == RELKIND_RELATION ||
! rel->rd_rel->relkind == RELKIND_MATVIEW);
/*
* Get the tuple descriptor and break down the tuple(s) into fields.
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 765,770 **** objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
--- 765,772 ----
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE);
objects = list_concat(objects, objs);
break;
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 3013,3018 **** getRelationDescription(StringInfo buffer, Oid relid)
--- 3013,3022 ----
appendStringInfo(buffer, _("view %s"),
relname);
break;
+ case RELKIND_MATVIEW:
+ appendStringInfo(buffer, _("materialized view %s"),
+ relname);
+ break;
case RELKIND_COMPOSITE_TYPE:
appendStringInfo(buffer, _("composite type %s"),
relname);
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
***************
*** 777,782 **** InsertPgClassTuple(Relation pg_class_desc,
--- 777,783 ----
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
+ values[Anum_pg_class_relisvalid - 1] = BoolGetDatum(rd_rel->relisvalid);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
if (relacl != (Datum) 0)
values[Anum_pg_class_relacl - 1] = relacl;
***************
*** 832,837 **** AddNewRelationTuple(Relation pg_class_desc,
--- 833,839 ----
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
case RELKIND_INDEX:
case RELKIND_TOASTVALUE:
/* The relation is real, but as yet empty */
***************
*** 855,860 **** AddNewRelationTuple(Relation pg_class_desc,
--- 857,863 ----
/* Initialize relfrozenxid */
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
relkind == RELKIND_TOASTVALUE)
{
/*
***************
*** 878,883 **** AddNewRelationTuple(Relation pg_class_desc,
--- 881,887 ----
new_rel_reltup->relowner = relowner;
new_rel_reltup->reltype = new_type_oid;
new_rel_reltup->reloftype = reloftype;
+ new_rel_reltup->relisvalid = true;
new_rel_desc->rd_att->tdtypeid = new_type_oid;
***************
*** 1056,1063 **** heap_create_with_catalog(const char *relname,
if (IsBinaryUpgrade &&
OidIsValid(binary_upgrade_next_heap_pg_class_oid) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE ||
! relkind == RELKIND_FOREIGN_TABLE))
{
relid = binary_upgrade_next_heap_pg_class_oid;
binary_upgrade_next_heap_pg_class_oid = InvalidOid;
--- 1060,1067 ----
if (IsBinaryUpgrade &&
OidIsValid(binary_upgrade_next_heap_pg_class_oid) &&
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
! relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
! relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
{
relid = binary_upgrade_next_heap_pg_class_oid;
binary_upgrade_next_heap_pg_class_oid = InvalidOid;
***************
*** 1083,1088 **** heap_create_with_catalog(const char *relname,
--- 1087,1093 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid,
relnamespace);
***************
*** 1126,1131 **** heap_create_with_catalog(const char *relname,
--- 1131,1137 ----
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_COMPOSITE_TYPE))
new_array_oid = AssignTypeArrayOid();
***************
*** 1303,1309 **** heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
! Assert(relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE);
heap_create_init_fork(new_rel_desc);
}
--- 1309,1316 ----
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
! Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
! relkind == RELKIND_TOASTVALUE);
heap_create_init_fork(new_rel_desc);
}
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 442,447 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 442,448 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
address =
get_relation_by_qualified_name(objtype, objname,
***************
*** 814,819 **** get_relation_by_qualified_name(ObjectType objtype, List *objname,
--- 815,827 ----
errmsg("\"%s\" is not a view",
RelationGetRelationName(relation))));
break;
+ case OBJECT_MATVIEW:
+ if (relation->rd_rel->relkind != RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a materialized view",
+ RelationGetRelationName(relation))));
+ break;
case OBJECT_FOREIGN_TABLE:
if (relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
***************
*** 1071,1076 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1079,1085 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
case OBJECT_COLUMN:
case OBJECT_RULE:
*** a/src/backend/catalog/toasting.c
--- b/src/backend/catalog/toasting.c
***************
*** 84,93 **** BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
! if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table",
relName)));
/* create_toast_table does all the work */
--- 84,94 ----
rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
! if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or materialized view",
relName)));
/* create_toast_table does all the work */
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
***************
*** 16,22 **** OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
! indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
--- 16,22 ----
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
! indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 118,123 **** ExecRenameStmt(RenameStmt *stmt)
--- 118,124 ----
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_INDEX:
case OBJECT_FOREIGN_TABLE:
RenameRelation(stmt);
***************
*** 189,194 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
--- 190,196 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
AlterTableNamespace(stmt);
break;
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 205,215 **** analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy)
}
/*
! * Check that it's a plain table or foreign table; we used to do this in
! * get_rel_oids() but seems safer to check after we've locked the
! * relation.
*/
! if (onerel->rd_rel->relkind == RELKIND_RELATION)
{
/* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
--- 205,216 ----
}
/*
! * Check that it's a plain table, materialized view, or foreign table; we
! * used to do this in get_rel_oids() but seems safer to check after we've
! * locked the relation.
*/
! if (onerel->rd_rel->relkind == RELKIND_RELATION ||
! onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
*** a/src/backend/commands/comment.c
--- b/src/backend/commands/comment.c
***************
*** 83,97 **** CommentObject(CommentStmt *stmt)
case OBJECT_COLUMN:
/*
! * Allow comments only on columns of tables, views, composite
! * types, and foreign tables (which are the only relkinds for
! * which pg_dump will dump per-column comments). In particular we
! * wish to disallow comments on index columns, because the naming
! * of an index's columns may change across PG versions, so dumping
! * per-column comments could create reload failures.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
--- 83,99 ----
case OBJECT_COLUMN:
/*
! * Allow comments only on columns of tables, views, materialized
! * views, composite types, and foreign tables (which are the only
! * relkinds for which pg_dump will dump per-column comments). In
! * particular we wish to disallow comments on index columns,
! * because the naming of an index's columns may change across PG
! * versions, so dumping per-column comments could create reload
! * failures.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
+ relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 1415,1420 **** BeginCopyTo(Relation rel,
--- 1415,1426 ----
errmsg("cannot copy from view \"%s\"",
RelationGetRelationName(rel)),
errhint("Try the COPY (SELECT ...) TO variant.")));
+ else if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from materialized view \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 1922,1927 **** CopyFrom(CopyState cstate)
--- 1928,1938 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy to view \"%s\"",
RelationGetRelationName(cstate->rel))));
+ else if (cstate->rel->rd_rel->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to materialized view \"%s\"",
+ RelationGetRelationName(cstate->rel))));
else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
*** a/src/backend/commands/createas.c
--- b/src/backend/commands/createas.c
***************
*** 2,7 ****
--- 2,9 ----
*
* createas.c
* Execution of CREATE TABLE ... AS, a/k/a SELECT INTO
+ * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors,
+ * implement that here, too.
*
* We implement this by diverting the query's normal output to a
* specialized DestReceiver type.
***************
*** 29,34 ****
--- 31,38 ----
#include "commands/createas.h"
#include "commands/prepare.h"
#include "commands/tablecmds.h"
+ #include "commands/view.h"
+ #include "parser/analyze.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteHandler.h"
#include "storage/smgr.h"
***************
*** 43,48 **** typedef struct
--- 47,53 ----
{
DestReceiver pub; /* publicly-known function pointers */
IntoClause *into; /* target relation specification */
+ Query *query; /* the query which defines/populates data */
/* These fields are filled by intorel_startup: */
Relation rel; /* relation to write to */
CommandId output_cid; /* cmin to insert in output tuples */
***************
*** 57,62 **** static void intorel_destroy(DestReceiver *self);
--- 62,130 ----
/*
+ * Common setup needed by both normal execution and EXPLAIN ANALYZE.
+ */
+ Query *
+ SetupForCreateTableAs(Query *query, IntoClause *into, const char *queryString,
+ ParamListInfo params, DestReceiver *dest)
+ {
+ Assert(query->commandType == CMD_SELECT);
+
+ /*
+ * Parse analysis was done already, but we still have to run the rule
+ * rewriter. We do not do AcquireRewriteLocks: we assume the query either
+ * came straight from the parser, or suitable locks were acquired by
+ * plancache.c.
+ *
+ * Because the rewriter and planner tend to scribble on the input, we make
+ * a preliminary copy of the source querytree. This prevents problems in
+ * the case that CTAS is in a portal or plpgsql function and is executed
+ * repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
+ */
+ if (into->relkind == RELKIND_MATVIEW)
+ query = (Query *) parse_analyze((Node *) copyObject(query),
+ queryString, NULL, 0)->utilityStmt;
+ else
+ {
+ List *rewritten;
+
+ rewritten = QueryRewrite((Query *) copyObject(query));
+
+ /* SELECT should never rewrite to more or less than one SELECT query */
+ if (list_length(rewritten) != 1)
+ elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
+ query = (Query *) linitial(rewritten);
+ }
+
+ Assert(query->commandType == CMD_SELECT);
+
+ /* Save the query after rewrite but before planning. */
+ ((DR_intorel *) dest)->query = query;
+ ((DR_intorel *) dest)->into = into;
+
+ if (into->relkind == RELKIND_MATVIEW)
+ {
+ /*
+ * A materialized view would either need to save parameters for use in
+ * maintaining or loading the data or prohibit them entirely. The
+ * latter seems safer and more sane.
+ */
+ if (params != NULL && params->numParams > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views may not be defined using bound parameters")));
+
+ /*
+ * For a materialized view, we don't want the planner scribbling on
+ * the query, because it will need to be planned again.
+ */
+ query = (Query *) copyObject(query);
+ }
+
+ return query;
+ }
+
+ /*
* ExecCreateTableAs -- execute a CREATE TABLE AS command
*/
void
***************
*** 66,72 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
Query *query = (Query *) stmt->query;
IntoClause *into = stmt->into;
DestReceiver *dest;
- List *rewritten;
PlannedStmt *plan;
QueryDesc *queryDesc;
ScanDirection dir;
--- 134,139 ----
***************
*** 90,115 **** ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
return;
}
- Assert(query->commandType == CMD_SELECT);
! /*
! * Parse analysis was done already, but we still have to run the rule
! * rewriter. We do not do AcquireRewriteLocks: we assume the query either
! * came straight from the parser, or suitable locks were acquired by
! * plancache.c.
! *
! * Because the rewriter and planner tend to scribble on the input, we make
! * a preliminary copy of the source querytree. This prevents problems in
! * the case that CTAS is in a portal or plpgsql function and is executed
! * repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
! */
! rewritten = QueryRewrite((Query *) copyObject(stmt->query));
!
! /* SELECT should never rewrite to more or less than one SELECT query */
! if (list_length(rewritten) != 1)
! elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
! query = (Query *) linitial(rewritten);
! Assert(query->commandType == CMD_SELECT);
/* plan the query */
plan = pg_plan_query(query, 0, params);
--- 157,164 ----
return;
}
! query = SetupForCreateTableAs(query, into, queryString, params, dest);
/* plan the query */
plan = pg_plan_query(query, 0, params);
***************
*** 299,310 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
if (lc != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("CREATE TABLE AS specifies too many column names")));
/*
* Actually create the target table
*/
! intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
/*
* If necessary, create a TOAST table for the target table. Note that
--- 348,399 ----
if (lc != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("too many column names are specified")));
!
! /*
! * Enforce validations needed for materialized views only.
! */
! if (into->relkind == RELKIND_MATVIEW)
! {
! /*
! * Prohibit a data-modifying CTE in the query used to create a
! * materialized view. It's not sufficiently clear what the user would
! * want to happen if the MV is refreshed or incrementally maintained.
! */
! if (myState->query->hasModifyingCTE)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("materialized views must not use data-modifying statements in WITH")));
!
! /*
! * Unlogged materialized views would be useful, but there has not yet
! * been a solution to the question of how to flag them as invalid
! * after a crash.
! */
! if (into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("unlogged materialized views are not supported")));
!
! /*
! * If the user didn't explicitly ask for a temporary materialized
! * view, check whether any temporary database objects are used in its
! * creation query. It would be hard to refresh data or incrementally
! * maintain it if a source disappeared.
! */
! if (into->rel->relpersistence == RELPERSISTENCE_PERMANENT
! && isQueryUsingTempRelation(myState->query))
! {
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("permanent materialized views must not use temporary tables or views")));
! }
! }
/*
* Actually create the target table
*/
! intoRelationId = DefineRelation(create, into->relkind, InvalidOid);
/*
* If necessary, create a TOAST table for the target table. Note that
***************
*** 330,335 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
--- 419,437 ----
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
/*
+ * Create the "view" part of a materialized view.
+ */
+ if (into->relkind == RELKIND_MATVIEW)
+ {
+ StoreViewQuery(intoRelationId, myState->query, false);
+ if (into->skipData)
+ {
+ CommandCounterIncrement();
+ SetRelationIsValid(intoRelationId, false);
+ }
+ }
+
+ /*
* Check INSERT permission on the constructed table.
*
* XXX: It would arguably make sense to skip this check if into->skipData
***************
*** 338,344 **** intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = intoRelationId;
! rte->relkind = RELKIND_RELATION;
rte->requiredPerms = ACL_INSERT;
for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
--- 440,446 ----
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = intoRelationId;
! rte->relkind = into->relkind;
rte->requiredPerms = ACL_INSERT;
for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 67,72 **** static event_trigger_support_data event_trigger_support[] = {
--- 67,73 ----
{ "FUNCTION", true },
{ "INDEX", true },
{ "LANGUAGE", true },
+ { "MATERIALIZED VIEW", true },
{ "OPERATOR", true },
{ "OPERATOR CLASS", true },
{ "OPERATOR FAMILY", true },
***************
*** 216,221 **** check_ddl_tag(const char *tag)
--- 217,223 ----
*/
if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
pg_strcasecmp(tag, "SELECT INTO") == 0 ||
+ pg_strcasecmp(tag, "LOAD MATERIALIZED VIEW") == 0 ||
pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
return EVENT_TRIGGER_COMMAND_TAG_OK;
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
***************
*** 47,53 **** explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
#define X_NOWHITESPACE 4
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
static double elapsed_time(instr_time *starttime);
--- 47,53 ----
#define X_NOWHITESPACE 4
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! const char *queryString, DestReceiver *dest, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
static double elapsed_time(instr_time *starttime);
***************
*** 218,224 **** ExplainQuery(ExplainStmt *stmt, const char *queryString,
foreach(l, rewritten)
{
ExplainOneQuery((Query *) lfirst(l), NULL, &es,
! queryString, params);
/* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
--- 218,224 ----
foreach(l, rewritten)
{
ExplainOneQuery((Query *) lfirst(l), NULL, &es,
! queryString, None_Receiver, params);
/* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
***************
*** 299,310 **** ExplainResultDesc(ExplainStmt *stmt)
*/
static void
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! const char *queryString, ParamListInfo params)
{
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
! ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
return;
}
--- 299,312 ----
*/
static void
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
! const char *queryString, DestReceiver *dest,
! ParamListInfo params)
{
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
! ExplainOneUtility(query->utilityStmt, into, es,
! queryString, dest, params);
return;
}
***************
*** 319,325 **** ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */
! ExplainOnePlan(plan, into, es, queryString, params);
}
}
--- 321,327 ----
plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */
! ExplainOnePlan(plan, into, es, queryString, dest, params);
}
}
***************
*** 336,342 **** ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
*/
void
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
! const char *queryString, ParamListInfo params)
{
if (utilityStmt == NULL)
return;
--- 338,345 ----
*/
void
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
! const char *queryString, DestReceiver *dest,
! ParamListInfo params)
{
if (utilityStmt == NULL)
return;
***************
*** 349,361 **** ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
* contained parsetree another time, but let's be safe.
*/
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
! List *rewritten;
Assert(IsA(ctas->query, Query));
! rewritten = QueryRewrite((Query *) copyObject(ctas->query));
! Assert(list_length(rewritten) == 1);
! ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
! queryString, params);
}
else if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
--- 352,366 ----
* contained parsetree another time, but let's be safe.
*/
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
! Query *query = (Query *) ctas->query;
!
! dest = CreateIntoRelDestReceiver(into);
Assert(IsA(ctas->query, Query));
!
! query = SetupForCreateTableAs(query, ctas->into, queryString, params, dest);
!
! ExplainOneQuery(query, ctas->into, es, queryString, dest, params);
}
else if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
***************
*** 396,404 **** ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
*/
void
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
! const char *queryString, ParamListInfo params)
{
- DestReceiver *dest;
QueryDesc *queryDesc;
instr_time starttime;
double totaltime = 0;
--- 401,408 ----
*/
void
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
! const char *queryString, DestReceiver *dest, ParamListInfo params)
{
QueryDesc *queryDesc;
instr_time starttime;
double totaltime = 0;
***************
*** 422,436 **** ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId();
- /*
- * Normally we discard the query's output, but if explaining CREATE TABLE
- * AS, we'd better use the appropriate tuple receiver.
- */
- if (into)
- dest = CreateIntoRelDestReceiver(into);
- else
- dest = None_Receiver;
-
/* Create a QueryDesc for the query */
queryDesc = CreateQueryDesc(plannedstmt, queryString,
GetActiveSnapshot(), InvalidSnapshot,
--- 426,431 ----
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 352,358 **** DefineIndex(IndexStmt *stmt,
relationId = RelationGetRelid(rel);
namespaceId = RelationGetNamespace(rel);
! if (rel->rd_rel->relkind != RELKIND_RELATION)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
--- 352,359 ----
relationId = RelationGetRelid(rel);
namespaceId = RelationGetNamespace(rel);
! if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_MATVIEW)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
***************
*** 1860,1866 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
{
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
! if (classtuple->relkind != RELKIND_RELATION)
continue;
/* Skip temp tables of other backends; we can't reindex them at all */
--- 1861,1868 ----
{
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
! if (classtuple->relkind != RELKIND_RELATION &&
! classtuple->relkind != RELKIND_MATVIEW)
continue;
/* Skip temp tables of other backends; we can't reindex them at all */
*** /dev/null
--- b/src/backend/commands/matview.c
***************
*** 0 ****
--- 1,314 ----
+ /*-------------------------------------------------------------------------
+ *
+ * matview.c
+ * materialized view support
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/matview.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/relscan.h"
+ #include "access/xact.h"
+ #include "catalog/catalog.h"
+ #include "catalog/namespace.h"
+ #include "commands/cluster.h"
+ #include "commands/matview.h"
+ #include "commands/tablecmds.h"
+ #include "executor/executor.h"
+ #include "miscadmin.h"
+ #include "storage/lmgr.h"
+ #include "storage/smgr.h"
+ #include "tcop/tcopprot.h"
+ #include "utils/snapmgr.h"
+
+
+ typedef struct
+ {
+ DestReceiver pub; /* publicly-known function pointers */
+ Oid transientoid; /* OID of new heap into which to store */
+ /* These fields are filled by transientrel_startup: */
+ Relation transientrel; /* relation to write to */
+ CommandId output_cid; /* cmin to insert in output tuples */
+ int hi_options; /* heap_insert performance options */
+ BulkInsertState bistate; /* bulk insert state */
+ } DR_transientrel;
+
+ static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
+ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
+ static void transientrel_shutdown(DestReceiver *self);
+ static void transientrel_destroy(DestReceiver *self);
+ static void load_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ Query *dataQuery, const char *queryString);
+
+ /*
+ * ExecLoadMatView -- execute a LOAD MATERIALIZED VIEW command
+ */
+ void
+ ExecLoadMatView(LoadMatViewStmt *stmt, const char *queryString,
+ ParamListInfo params, char *completionTag)
+ {
+ Oid matviewOid;
+ Relation matviewRel;
+ RewriteRule *rule;
+ List *actions;
+ Query *dataQuery;
+ Oid tableSpace;
+ bool isWithOids;
+
+ /*
+ * Get a lock until end of transaction.
+ */
+ matviewOid = RangeVarGetRelidExtended(stmt->relation,
+ AccessExclusiveLock, false, false,
+ RangeVarCallbackOwnsTable, NULL);
+ matviewRel = heap_open(matviewOid, NoLock);
+
+ /* Make sure it is a materialized view. */
+ if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("\"%s\" is not a materialized view",
+ RelationGetRelationName(matviewRel))));
+
+ /*
+ * We're not using materialized views in the system catalogs.
+ */
+ Assert(!IsSystemRelation(matviewRel));
+
+ /*
+ * Reject clustering a remote temp table ... their local buffer
+ * manager is not going to cope.
+ */
+ if (RELATION_IS_OTHER_TEMP(matviewRel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot load temporary materialized views of other sessions")));
+
+ /*
+ * Check that everything is correct for a load. Problems at this point are
+ * internal errors, so elog is sufficient.
+ */
+ if (matviewRel->rd_rel->relhasrules == false ||
+ matviewRel->rd_rules->numLocks < 1)
+ elog(ERROR,
+ "materialized view \"%s\" is missing rewrite information",
+ RelationGetRelationName(matviewRel));
+
+ if (matviewRel->rd_rules->numLocks > 1)
+ elog(ERROR,
+ "materialized view \"%s\" has too many rules",
+ RelationGetRelationName(matviewRel));
+
+ rule = matviewRel->rd_rules->rules[0];
+ if (rule->event != CMD_SELECT || !(rule->isInstead))
+ elog(ERROR,
+ "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule",
+ RelationGetRelationName(matviewRel));
+
+ actions = rule->actions;
+ if (list_length(actions) != 1)
+ elog(ERROR,
+ "the rule for materialized view \"%s\" is not a single action",
+ RelationGetRelationName(matviewRel));
+
+ /*
+ * The stored query was rewritten at the time of the MV definition, but
+ * has not been scribbled on by the planner.
+ */
+ dataQuery = (Query *) linitial(actions);
+ Assert(IsA(dataQuery, Query));
+
+ /*
+ * Check for active uses of the relation in the current transaction, such
+ * as open scans.
+ */
+ CheckTableNotInUse(matviewRel, "LOAD MATERIALIZED VIEW");
+
+ tableSpace = matviewRel->rd_rel->reltablespace;
+ isWithOids = matviewRel->rd_rel->relhasoids;
+
+ heap_close(matviewRel, NoLock);
+
+ load_matview(matviewOid, tableSpace, isWithOids, dataQuery, queryString);
+ }
+
+ /*
+ * load_matview
+ *
+ * This loads the materialized view by creating a new table and swapping the
+ * relfilenodes of the new table and the old materialized view, so the OID of
+ * the original materialized view is preserved. Thus we do not lose GRANT nor
+ * references to this materialized view.
+ *
+ * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
+ * the new heap, it's better to create the indexes afterwards than to fill them
+ * incrementally while we load.
+ *
+ * If the materialized view was flagged with relisvalid == false, success of
+ * this command will change it to true.
+ */
+ static void
+ load_matview(Oid matviewOid, Oid tableSpace, bool isWithOids,
+ Query *dataQuery, const char *queryString)
+ {
+ Oid OIDNewHeap;
+ PlannedStmt *plan;
+ DestReceiver *dest;
+ QueryDesc *queryDesc;
+
+ /* Check for user-requested abort. */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Plan the query which will generate data for the load. */
+ plan = pg_plan_query(dataQuery, 0, NULL);
+
+ /* Create the transient table that will receive the regenerated data. */
+ OIDNewHeap = make_new_heap(matviewOid, tableSpace);
+ dest = CreateTransientRelDestReceiver(OIDNewHeap);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees
+ * results of any previously executed queries. (This could only matter if
+ * the planner executed an allegedly-stable function that changed the
+ * database contents, but let's do it anyway to be safe.)
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ /* Create a QueryDesc, redirecting output to our tuple receiver */
+ queryDesc = CreateQueryDesc(plan, queryString,
+ GetActiveSnapshot(), InvalidSnapshot,
+ dest, NULL, 0);
+
+ /* call ExecutorStart to prepare the plan for execution */
+ ExecutorStart(queryDesc,
+ isWithOids ? EXEC_FLAG_WITH_OIDS : EXEC_FLAG_WITHOUT_OIDS);
+
+ /* run the plan */
+ ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+
+ /* and clean up */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+
+ /*
+ * Swap the physical files of the target and transient tables, then
+ * rebuild the target's indexes and throw away the transient table.
+ */
+ finish_heap_swap(matviewOid, OIDNewHeap, false, false, false, RecentXmin);
+
+ SetRelationIsValid(matviewOid, true);
+ }
+
+ DestReceiver *
+ CreateTransientRelDestReceiver(Oid transientoid)
+ {
+ DR_transientrel *self = (DR_transientrel *) palloc0(sizeof(DR_transientrel));
+
+ self->pub.receiveSlot = transientrel_receive;
+ self->pub.rStartup = transientrel_startup;
+ self->pub.rShutdown = transientrel_shutdown;
+ self->pub.rDestroy = transientrel_destroy;
+ self->pub.mydest = DestTransientRel;
+ self->transientoid = transientoid;
+
+ return (DestReceiver *) self;
+ }
+
+ /*
+ * transientrel_startup --- executor startup
+ */
+ static void
+ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+ {
+ DR_transientrel *myState = (DR_transientrel *) self;
+ Relation transientrel;
+
+ transientrel = heap_open(myState->transientoid, NoLock);
+
+ /*
+ * Fill private fields of myState for use by later routines
+ */
+ myState->transientrel = transientrel;
+ myState->output_cid = GetCurrentCommandId(true);
+
+ /*
+ * We can skip WAL-logging the insertions, unless PITR or streaming
+ * replication is in use. We can skip the FSM in any case.
+ */
+ myState->hi_options = HEAP_INSERT_SKIP_FSM |
+ (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
+ myState->bistate = GetBulkInsertState();
+
+ /* Not using WAL requires smgr_targblock be initially invalid */
+ Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
+ }
+
+ /*
+ * transientrel_receive --- receive one tuple
+ */
+ static void
+ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
+ {
+ DR_transientrel *myState = (DR_transientrel *) self;
+ HeapTuple tuple;
+
+ /*
+ * get the heap tuple out of the tuple table slot, making sure we have a
+ * writable copy
+ */
+ tuple = ExecMaterializeSlot(slot);
+
+ /*
+ * force assignment of new OID (see comments in ExecInsert)
+ */
+ if (myState->transientrel->rd_rel->relhasoids)
+ HeapTupleSetOid(tuple, InvalidOid);
+
+ heap_insert(myState->transientrel,
+ tuple,
+ myState->output_cid,
+ myState->hi_options,
+ myState->bistate);
+
+ /* We know this is a newly created relation, so there are no indexes */
+ }
+
+ /*
+ * transientrel_shutdown --- executor end
+ */
+ static void
+ transientrel_shutdown(DestReceiver *self)
+ {
+ DR_transientrel *myState = (DR_transientrel *) self;
+
+ FreeBulkInsertState(myState->bistate);
+
+ /* If we skipped using WAL, must heap_sync before commit */
+ if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
+ heap_sync(myState->transientrel);
+
+ /* close transientrel, but keep lock until commit */
+ heap_close(myState->transientrel, NoLock);
+ myState->transientrel = NULL;
+ }
+
+ /*
+ * transientrel_destroy --- release DestReceiver object
+ */
+ static void
+ transientrel_destroy(DestReceiver *self)
+ {
+ pfree(self);
+ }
*** a/src/backend/commands/prepare.c
--- b/src/backend/commands/prepare.c
***************
*** 665,673 **** ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt))
! ExplainOnePlan(pstmt, into, es, query_string, paramLI);
else
! ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
--- 665,673 ----
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt))
! ExplainOnePlan(pstmt, into, es, query_string, None_Receiver, paramLI);
else
! ExplainOneUtility((Node *) pstmt, into, es, query_string, None_Receiver, paramLI);
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
*** a/src/backend/commands/seclabel.c
--- b/src/backend/commands/seclabel.c
***************
*** 101,111 **** ExecSecLabelStmt(SecLabelStmt *stmt)
/*
* Allow security labels only on columns of tables, views,
! * composite types, and foreign tables (which are the only
! * relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
--- 101,112 ----
/*
* Allow security labels only on columns of tables, views,
! * materialized views, composite types, and foreign tables (which
! * are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
+ relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 215,220 **** static const struct dropmsgstrings dropmsgstringarray[] = {
--- 215,226 ----
gettext_noop("view \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a view"),
gettext_noop("Use DROP VIEW to remove a view.")},
+ {RELKIND_MATVIEW,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("materialized view \"%s\" does not exist"),
+ gettext_noop("materialized view \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a materialized view"),
+ gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
{RELKIND_INDEX,
ERRCODE_UNDEFINED_OBJECT,
gettext_noop("index \"%s\" does not exist"),
***************
*** 246,254 **** struct DropRelationCallbackState
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
! #define ATT_INDEX 0x0004
! #define ATT_COMPOSITE_TYPE 0x0008
! #define ATT_FOREIGN_TABLE 0x0010
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
--- 252,261 ----
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
! #define ATT_MATVIEW 0x0004
! #define ATT_INDEX 0x0008
! #define ATT_COMPOSITE_TYPE 0x0010
! #define ATT_FOREIGN_TABLE 0x0020
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
***************
*** 397,402 **** static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
--- 404,411 ----
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+ static bool isQueryUsingTempRelation_walker(Node *node, void *context);
+
/* ----------------------------------------------------------------
* DefineRelation
***************
*** 733,739 **** DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
/*
* RemoveRelations
* Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
! * DROP FOREIGN TABLE
*/
void
RemoveRelations(DropStmt *drop)
--- 742,748 ----
/*
* RemoveRelations
* Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
! * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
*/
void
RemoveRelations(DropStmt *drop)
***************
*** 782,787 **** RemoveRelations(DropStmt *drop)
--- 791,800 ----
relkind = RELKIND_VIEW;
break;
+ case OBJECT_MATVIEW:
+ relkind = RELKIND_MATVIEW;
+ break;
+
case OBJECT_FOREIGN_TABLE:
relkind = RELKIND_FOREIGN_TABLE;
break;
***************
*** 1163,1168 **** ExecuteTruncate(TruncateStmt *stmt)
--- 1176,1188 ----
heap_relid = RelationGetRelid(rel);
toast_relid = rel->rd_rel->reltoastrelid;
+ /* This makes a materialized view invalid for use. */
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
+ rel->rd_rel->relisvalid)
+ {
+ SetRelationIsValid(heap_relid, false);
+ }
+
/*
* The same for the toast table, if any.
*/
***************
*** 1226,1236 **** truncate_check_rel(Relation rel)
{
AclResult aclresult;
! /* Only allow truncate on regular tables */
! if (rel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table",
RelationGetRelationName(rel))));
/* Permissions checks */
--- 1246,1257 ----
{
AclResult aclresult;
! /* Only allow truncate on regular tables and materialized views. */
! if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or materialized view",
RelationGetRelationName(rel))));
/* Permissions checks */
***************
*** 2044,2049 **** SetRelationHasSubclass(Oid relationId, bool relhassubclass)
--- 2065,2117 ----
}
/*
+ * SetRelationIsValid
+ * Set the value of the relation's relisvalid field in pg_class.
+ *
+ * NOTE: caller must be holding an appropriate lock on the relation.
+ * ShareUpdateExclusiveLock is sufficient.
+ *
+ * NOTE: an important side-effect of this operation is that an SI invalidation
+ * message is sent out to all backends --- including me --- causing plans
+ * referencing the relation to be rebuilt with the new list of children.
+ * This must happen even if we find that no change is needed in the pg_class
+ * row.
+ */
+ void
+ SetRelationIsValid(Oid relationId, bool relisvalid)
+ {
+ Relation relationRelation;
+ HeapTuple tuple;
+ Form_pg_class classtuple;
+
+ /*
+ * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+ */
+ relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relationId);
+ classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (classtuple->relisvalid != relisvalid)
+ {
+ classtuple->relisvalid = relisvalid;
+ simple_heap_update(relationRelation, &tuple->t_self, tuple);
+
+ /* keep the catalog indexes up to date */
+ CatalogUpdateIndexes(relationRelation, tuple);
+ }
+ else
+ {
+ /* no need to change tuple, but force relcache rebuild anyway */
+ CacheInvalidateRelcacheByTuple(tuple);
+ }
+
+ heap_freetuple(tuple);
+ heap_close(relationRelation, RowExclusiveLock);
+ }
+
+ /*
* renameatt_check - basic sanity checks before attribute rename
*/
static void
***************
*** 2065,2076 **** renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
*/
if (relkind != RELKIND_RELATION &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
relkind != RELKIND_INDEX &&
relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type, index, or foreign table",
NameStr(classform->relname))));
/*
--- 2133,2145 ----
*/
if (relkind != RELKIND_RELATION &&
relkind != RELKIND_VIEW &&
+ relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
relkind != RELKIND_INDEX &&
relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table",
NameStr(classform->relname))));
/*
***************
*** 2978,2989 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
! ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
! ATSimplePermissions(rel, ATT_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
--- 3047,3058 ----
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
! ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
! ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
***************
*** 2996,3002 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
! ATSimplePermissions(rel, ATT_TABLE);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
--- 3065,3071 ----
pass = AT_PASS_DROP;
break;
case AT_AddIndex: /* ADD INDEX */
! ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
***************
*** 3043,3049 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
! ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
--- 3112,3118 ----
break;
case AT_ClusterOn: /* CLUSTER ON */
case AT_DropCluster: /* SET WITHOUT CLUSTER */
! ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
/* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
***************
*** 3070,3076 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
! ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
--- 3139,3145 ----
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
! ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
***************
*** 3078,3084 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
case AT_ReplaceRelOptions: /* reset them all, then set just these */
! ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
--- 3147,3153 ----
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
case AT_ReplaceRelOptions: /* reset them all, then set just these */
! ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
***************
*** 3191,3197 **** ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
! if (tab->relkind == RELKIND_RELATION)
AlterTableCreateToastTable(tab->relid, (Datum) 0);
}
}
--- 3260,3267 ----
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
! if (tab->relkind == RELKIND_RELATION ||
! tab->relkind == RELKIND_MATVIEW)
AlterTableCreateToastTable(tab->relid, (Datum) 0);
}
}
***************
*** 3923,3928 **** ATSimplePermissions(Relation rel, int allowed_targets)
--- 3993,4001 ----
case RELKIND_VIEW:
actual_target = ATT_VIEW;
break;
+ case RELKIND_MATVIEW:
+ actual_target = ATT_MATVIEW;
+ break;
case RELKIND_INDEX:
actual_target = ATT_INDEX;
break;
***************
*** 3969,3986 **** ATWrongRelkindError(Relation rel, int allowed_targets)
case ATT_TABLE:
msg = _("\"%s\" is not a table");
break;
- case ATT_TABLE | ATT_INDEX:
- msg = _("\"%s\" is not a table or index");
- break;
case ATT_TABLE | ATT_VIEW:
msg = _("\"%s\" is not a table or view");
break;
case ATT_TABLE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table or foreign table");
break;
case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table, composite type, or foreign table");
break;
case ATT_VIEW:
msg = _("\"%s\" is not a view");
break;
--- 4042,4068 ----
case ATT_TABLE:
msg = _("\"%s\" is not a table");
break;
case ATT_TABLE | ATT_VIEW:
msg = _("\"%s\" is not a table or view");
break;
+ case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX:
+ msg = _("\"%s\" is not a table, view, materialized view, or index");
+ break;
+ case ATT_TABLE | ATT_MATVIEW:
+ msg = _("\"%s\" is not a table or materialized view");
+ break;
+ case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
+ msg = _("\"%s\" is not a table, materialized view, or index");
+ break;
case ATT_TABLE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table or foreign table");
break;
case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table, composite type, or foreign table");
break;
+ case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table");
+ break;
case ATT_VIEW:
msg = _("\"%s\" is not a view");
break;
***************
*** 4133,4139 **** find_composite_type_dependencies(Oid typeOid, Relation origRelation,
rel = relation_open(pg_depend->objid, AccessShareLock);
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
! if (rel->rd_rel->relkind == RELKIND_RELATION)
{
if (origTypeName)
ereport(ERROR,
--- 4215,4222 ----
rel = relation_open(pg_depend->objid, AccessShareLock);
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
! if (rel->rd_rel->relkind == RELKIND_RELATION ||
! rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
ereport(ERROR,
***************
*** 4959,4969 **** ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, index, or foreign table",
RelationGetRelationName(rel))));
/* Permissions checks */
--- 5042,5053 ----
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, materialized view, index, or foreign table",
RelationGetRelationName(rel))));
/* Permissions checks */
***************
*** 8064,8069 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
--- 8148,8154 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
/* ok to change owner */
break;
***************
*** 8220,8230 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
/*
! * If we are operating on a table, also change the ownership of any
! * indexes and sequences that belong to the table, as well as the
! * table's toast table (if it has one)
*/
if (tuple_class->relkind == RELKIND_RELATION ||
tuple_class->relkind == RELKIND_TOASTVALUE)
{
List *index_oid_list;
--- 8305,8316 ----
tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
/*
! * If we are operating on a table or materialized view, also change
! * the ownership of any indexes and sequences that belong to the
! * relation, as well as its toast table (if it has one).
*/
if (tuple_class->relkind == RELKIND_RELATION ||
+ tuple_class->relkind == RELKIND_MATVIEW ||
tuple_class->relkind == RELKIND_TOASTVALUE)
{
List *index_oid_list;
***************
*** 8240,8246 **** ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
list_free(index_oid_list);
}
! if (tuple_class->relkind == RELKIND_RELATION)
{
/* If it has a toast table, recurse to change its ownership */
if (tuple_class->reltoastrelid != InvalidOid)
--- 8326,8333 ----
list_free(index_oid_list);
}
! if (tuple_class->relkind == RELKIND_RELATION ||
! tuple_class->relkind == RELKIND_MATVIEW)
{
/* If it has a toast table, recurse to change its ownership */
if (tuple_class->reltoastrelid != InvalidOid)
***************
*** 8510,8515 **** ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
--- 8597,8603 ----
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
break;
case RELKIND_INDEX:
***************
*** 8518,8524 **** ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, index, or TOAST table",
RelationGetRelationName(rel))));
break;
}
--- 8606,8612 ----
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table",
RelationGetRelationName(rel))));
break;
}
***************
*** 9785,9792 **** AlterTableNamespace(AlterObjectSchemaStmt *stmt)
}
/*
! * The guts of relocating a table to another namespace: besides moving
! * the table itself, its dependent objects are relocated to the new schema.
*/
void
AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
--- 9873,9881 ----
}
/*
! * The guts of relocating a table or materialized view to another namespace:
! * besides moving the relation itself, its dependent objects are relocated to
! * the new schema.
*/
void
AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
***************
*** 9807,9813 **** AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
nspOid, false, false, objsMoved);
/* Fix other dependent stuff */
! if (rel->rd_rel->relkind == RELKIND_RELATION)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
--- 9896,9903 ----
nspOid, false, false, objsMoved);
/* Fix other dependent stuff */
! if (rel->rd_rel->relkind == RELKIND_RELATION ||
! rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
***************
*** 10212,10221 **** AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
/*
* This is intended as a callback for RangeVarGetRelidExtended(). It allows
! * the table to be locked only if (1) it's a plain table or TOAST table and
! * (2) the current user is the owner (or the superuser). This meets the
! * permission-checking needs of both CLUSTER and REINDEX TABLE; we expose it
! * here so that it can be used by both.
*/
void
RangeVarCallbackOwnsTable(const RangeVar *relation,
--- 10302,10312 ----
/*
* This is intended as a callback for RangeVarGetRelidExtended(). It allows
! * the relation to be locked only if (1) it's a plain table, materialized
! * view, or TOAST table and (2) the current user is the owner (or the
! * superuser). This meets the permission-checking needs of CLUSTER, REINDEX
! * TABLE, and LOAD MATERIALIZED VIEW; we expose it here so that it can be used
! * by all.
*/
void
RangeVarCallbackOwnsTable(const RangeVar *relation,
***************
*** 10235,10244 **** RangeVarCallbackOwnsTable(const RangeVar *relation,
relkind = get_rel_relkind(relId);
if (!relkind)
return;
! if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table", relation->relname)));
/* Check permissions */
if (!pg_class_ownercheck(relId, GetUserId()))
--- 10326,10336 ----
relkind = get_rel_relkind(relId);
if (!relkind)
return;
! if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
! relkind != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or materialized view", relation->relname)));
/* Check permissions */
if (!pg_class_ownercheck(relId, GetUserId()))
***************
*** 10320,10325 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10412,10422 ----
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a view", rv->relname)));
+ if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a materialized view", rv->relname)));
+
if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 10356,10364 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
* Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
* to a different schema, such as indexes and TOAST tables.
*/
! if (IsA(stmt, AlterObjectSchemaStmt) &&relkind != RELKIND_RELATION
! && relkind != RELKIND_VIEW && relkind != RELKIND_SEQUENCE
! && relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, sequence, or foreign table",
--- 10453,10461 ----
* Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
* to a different schema, such as indexes and TOAST tables.
*/
! if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION
! && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW
! && relkind != RELKIND_SEQUENCE && relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, sequence, or foreign table",
***************
*** 10366,10368 **** RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
--- 10463,10513 ----
ReleaseSysCache(tuple);
}
+
+ /*
+ * Returns true iff any relation underlying this query is a temporary database
+ * object (table, view, or materialized view).
+ *
+ */
+ bool
+ isQueryUsingTempRelation(Query *query)
+ {
+ return isQueryUsingTempRelation_walker((Node *) query, NULL);
+ }
+
+ static bool
+ isQueryUsingTempRelation_walker(Node *node, void *context)
+ {
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Query))
+ {
+ Query *query = (Query *) node;
+ ListCell *rtable;
+
+ foreach(rtable, query->rtable)
+ {
+ RangeTblEntry *rte = lfirst(rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ Relation rel = heap_open(rte->relid, AccessShareLock);
+ char relpersistence = rel->rd_rel->relpersistence;
+
+ heap_close(rel, AccessShareLock);
+ if (relpersistence == RELPERSISTENCE_TEMP)
+ return true;
+ }
+ }
+
+ return query_tree_walker(query,
+ isQueryUsingTempRelation_walker,
+ context,
+ QTW_IGNORE_JOINALIASES);
+ }
+
+ return expression_tree_walker(node,
+ isQueryUsingTempRelation_walker,
+ context);
+ }
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2746,2752 **** get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
format_type_be(domainOid));
/* Otherwise we can ignore views, composite types, etc */
! if (rel->rd_rel->relkind != RELKIND_RELATION)
{
relation_close(rel, lockmode);
continue;
--- 2746,2753 ----
format_type_be(domainOid));
/* Otherwise we can ignore views, composite types, etc */
! if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_MATVIEW)
{
relation_close(rel, lockmode);
continue;
*** a/src/backend/commands/vacuum.c
--- b/src/backend/commands/vacuum.c
***************
*** 340,362 **** get_rel_oids(Oid relid, const RangeVar *vacrel)
}
else
{
! /* Process all plain relations listed in pg_class */
Relation pgclass;
HeapScanDesc scan;
HeapTuple tuple;
- ScanKeyData key;
-
- ScanKeyInit(&key,
- Anum_pg_class_relkind,
- BTEqualStrategyNumber, F_CHAREQ,
- CharGetDatum(RELKIND_RELATION));
pgclass = heap_open(RelationRelationId, AccessShareLock);
! scan = heap_beginscan(pgclass, SnapshotNow, 1, &key);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
--- 340,365 ----
}
else
{
! /*
! * Process all plain relations and materialized views listed in
! * pg_class
! */
Relation pgclass;
HeapScanDesc scan;
HeapTuple tuple;
pgclass = heap_open(RelationRelationId, AccessShareLock);
! scan = heap_beginscan(pgclass, SnapshotNow, 0, NULL);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
+ Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (classForm->relkind != RELKIND_RELATION &&
+ classForm->relkind != RELKIND_MATVIEW)
+ continue;
+
/* Make a relation list entry for this guy */
oldcontext = MemoryContextSwitchTo(vac_context);
oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
***************
*** 706,711 **** vac_update_datfrozenxid(void)
--- 709,715 ----
* InvalidTransactionId in relfrozenxid anyway.)
*/
if (classForm->relkind != RELKIND_RELATION &&
+ classForm->relkind != RELKIND_MATVIEW &&
classForm->relkind != RELKIND_TOASTVALUE)
continue;
***************
*** 983,988 **** vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound)
--- 987,993 ----
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
ereport(WARNING,
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 36,92 ****
static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc);
- static bool isViewOnTempTable_walker(Node *node, void *context);
-
- /*---------------------------------------------------------------------
- * isViewOnTempTable
- *
- * Returns true iff any of the relations underlying this view are
- * temporary tables.
- *---------------------------------------------------------------------
- */
- static bool
- isViewOnTempTable(Query *viewParse)
- {
- return isViewOnTempTable_walker((Node *) viewParse, NULL);
- }
-
- static bool
- isViewOnTempTable_walker(Node *node, void *context)
- {
- if (node == NULL)
- return false;
-
- if (IsA(node, Query))
- {
- Query *query = (Query *) node;
- ListCell *rtable;
-
- foreach(rtable, query->rtable)
- {
- RangeTblEntry *rte = lfirst(rtable);
-
- if (rte->rtekind == RTE_RELATION)
- {
- Relation rel = heap_open(rte->relid, AccessShareLock);
- char relpersistence = rel->rd_rel->relpersistence;
-
- heap_close(rel, AccessShareLock);
- if (relpersistence == RELPERSISTENCE_TEMP)
- return true;
- }
- }
-
- return query_tree_walker(query,
- isViewOnTempTable_walker,
- context,
- QTW_IGNORE_JOINALIASES);
- }
-
- return expression_tree_walker(node,
- isViewOnTempTable_walker,
- context);
- }
/*---------------------------------------------------------------------
* DefineVirtualRelation
--- 36,41 ----
***************
*** 506,512 **** DefineView(ViewStmt *stmt, const char *queryString)
*/
view = copyObject(stmt->view); /* don't corrupt original command */
if (view->relpersistence == RELPERSISTENCE_PERMANENT
! && isViewOnTempTable(viewParse))
{
view->relpersistence = RELPERSISTENCE_TEMP;
ereport(NOTICE,
--- 455,461 ----
*/
view = copyObject(stmt->view); /* don't corrupt original command */
if (view->relpersistence == RELPERSISTENCE_PERMANENT
! && isQueryUsingTempRelation(viewParse))
{
view->relpersistence = RELPERSISTENCE_TEMP;
ereport(NOTICE,
***************
*** 530,535 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 479,493 ----
*/
CommandCounterIncrement();
+ StoreViewQuery(viewOid, viewParse, stmt->replace);
+ }
+
+ /*
+ * Use the rules system to store the query for the view.
+ */
+ void
+ StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
+ {
/*
* The range table of 'viewParse' does not contain entries for the "OLD"
* and "NEW" relations. So... add them!
***************
*** 539,543 **** DefineView(ViewStmt *stmt, const char *queryString)
/*
* Now create the rules associated with the view.
*/
! DefineViewRules(viewOid, viewParse, stmt->replace);
}
--- 497,501 ----
/*
* Now create the rules associated with the view.
*/
! DefineViewRules(viewOid, viewParse, replace);
}
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 490,495 **** ExecutorRewind(QueryDesc *queryDesc)
--- 490,532 ----
/*
+ * ExecCheckRelationsValid
+ * Check that relations which are to be accessed are flagged as valid.
+ *
+ * If not, throw error. For a materialized view, suggest refresh.
+ */
+ static void
+ ExecCheckRelationsValid(List *rangeTable)
+ {
+ /*
+ ListCell *l;
+
+ foreach(l, rangeTable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+
+ if (rte->rtekind != RTE_RELATION)
+ continue;
+
+ if (!RelationIsFlaggedAsValid(rte->relid))
+ {
+ if (rte->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("materialized view \"%s\" has not been populated",
+ get_rel_name(rte->relid)),
+ errhint("Use the LOAD MATERIALIZED VIEW command.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("relation \"%s\" is flagged as invalid",
+ get_rel_name(rte->relid))));
+ }
+ }
+ */
+ }
+
+ /*
* ExecCheckRTPerms
* Check access permissions for all relations listed in a range table.
*
***************
*** 724,729 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 761,771 ----
ExecCheckRTPerms(rangeTable, true);
/*
+ * Ensure that all referrenced relations are flagged as valid.
+ */
+ ExecCheckRelationsValid(rangeTable);
+
+ /*
* initialize the node's execution state
*/
estate->es_range_table = rangeTable;
***************
*** 984,989 **** CheckValidResultRel(Relation resultRel, CmdType operation)
--- 1026,1037 ----
break;
}
break;
+ case RELKIND_MATVIEW:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change materialized view \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
case RELKIND_FOREIGN_TABLE:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 1034,1039 **** CheckValidRowMarkRel(Relation rel, RowMarkType markType)
--- 1082,1094 ----
errmsg("cannot lock rows in view \"%s\"",
RelationGetRelationName(rel))));
break;
+ case RELKIND_MATVIEW:
+ /* Should not get here */
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot lock rows in materialized view \"%s\"",
+ RelationGetRelationName(rel))));
+ break;
case RELKIND_FOREIGN_TABLE:
/* Perhaps we can support this someday, but not today */
ereport(ERROR,
*** a/src/backend/executor/spi.c
--- b/src/backend/executor/spi.c
***************
*** 1941,1946 **** _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
--- 1941,1953 ----
if (((CreateTableAsStmt *) stmt)->is_select_into)
res = SPI_OK_SELINTO;
}
+ else if (IsA(stmt, LoadMatViewStmt))
+ {
+ Assert(strncmp(completionTag,
+ "LOAD MATERIALIZED VIEW ", 23) == 0);
+ _SPI_current->processed = strtoul(completionTag + 23,
+ NULL, 10);
+ }
else if (IsA(stmt, CopyStmt))
{
Assert(strncmp(completionTag, "COPY ", 5) == 0);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1032,1037 **** _copyIntoClause(const IntoClause *from)
--- 1032,1038 ----
COPY_SCALAR_FIELD(onCommit);
COPY_STRING_FIELD(tableSpaceName);
COPY_SCALAR_FIELD(skipData);
+ COPY_SCALAR_FIELD(relkind);
return newnode;
}
***************
*** 3226,3236 **** _copyCreateTableAsStmt(const CreateTableAsStmt *from)
--- 3227,3248 ----
COPY_NODE_FIELD(query);
COPY_NODE_FIELD(into);
+ COPY_SCALAR_FIELD(relkind);
COPY_SCALAR_FIELD(is_select_into);
return newnode;
}
+ static LoadMatViewStmt *
+ _copyLoadMatViewStmt(const LoadMatViewStmt *from)
+ {
+ LoadMatViewStmt *newnode = makeNode(LoadMatViewStmt);
+
+ COPY_NODE_FIELD(relation);
+
+ return newnode;
+ }
+
static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from)
{
***************
*** 4301,4306 **** copyObject(const void *from)
--- 4313,4321 ----
case T_CreateTableAsStmt:
retval = _copyCreateTableAsStmt(from);
break;
+ case T_LoadMatViewStmt:
+ retval = _copyLoadMatViewStmt(from);
+ break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 124,129 **** _equalIntoClause(const IntoClause *a, const IntoClause *b)
--- 124,130 ----
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
COMPARE_SCALAR_FIELD(skipData);
+ COMPARE_SCALAR_FIELD(relkind);
return true;
}
***************
*** 1523,1534 **** _equalCreateTableAsStmt(const CreateTableAsStmt *a, const CreateTableAsStmt *b)
--- 1524,1544 ----
{
COMPARE_NODE_FIELD(query);
COMPARE_NODE_FIELD(into);
+ COMPARE_SCALAR_FIELD(relkind);
COMPARE_SCALAR_FIELD(is_select_into);
return true;
}
static bool
+ _equalLoadMatViewStmt(const LoadMatViewStmt *a, const LoadMatViewStmt *b)
+ {
+ COMPARE_NODE_FIELD(relation);
+
+ return true;
+ }
+
+ static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
COMPARE_NODE_FIELD(sequence);
***************
*** 2788,2793 **** equal(const void *a, const void *b)
--- 2798,2806 ----
case T_CreateTableAsStmt:
retval = _equalCreateTableAsStmt(a, b);
break;
+ case T_LoadMatViewStmt:
+ retval = _equalLoadMatViewStmt(a, b);
+ break;
case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b);
break;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 893,898 **** _outIntoClause(StringInfo str, const IntoClause *node)
--- 893,899 ----
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
WRITE_BOOL_FIELD(skipData);
+ WRITE_CHAR_FIELD(relkind);
}
static void
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 395,400 **** _readIntoClause(void)
--- 395,401 ----
READ_ENUM_FIELD(onCommit, OnCommitAction);
READ_STRING_FIELD(tableSpaceName);
READ_BOOL_FIELD(skipData);
+ READ_CHAR_FIELD(relkind);
READ_DONE();
}
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 3375,3381 **** plan_cluster_use_sort(Oid tableOid, Oid indexOid)
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = tableOid;
! rte->relkind = RELKIND_RELATION;
rte->lateral = false;
rte->inh = false;
rte->inFromCl = true;
--- 3375,3381 ----
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = tableOid;
! rte->relkind = RELKIND_RELATION; /* Don't be too picky. */
rte->lateral = false;
rte->inh = false;
rte->inFromCl = true;
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 396,401 **** estimate_rel_size(Relation rel, int32 *attr_widths,
--- 396,402 ----
{
case RELKIND_RELATION:
case RELKIND_INDEX:
+ case RELKIND_MATVIEW:
case RELKIND_TOASTVALUE:
/* it has storage, ok to call the smgr */
curpages = RelationGetNumberOfBlocks(rel);
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 190,195 **** transformTopLevelStmt(ParseState *pstate, Node *parseTree)
--- 190,196 ----
ctas->query = parseTree;
ctas->into = stmt->intoClause;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = true;
/*
***************
*** 324,329 **** analyze_requires_snapshot(Node *parseTree)
--- 325,335 ----
result = true;
break;
+ case T_LoadMatViewStmt:
+ /* yes, because the SELECT from pg_rewrite must be analyzed */
+ result = true;
+ break;
+
default:
/* other utility statements don't have any real parse analysis */
result = false;
***************
*** 2117,2123 **** transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
/*
* transformCreateTableAsStmt -
! * transform a CREATE TABLE AS (or SELECT ... INTO) Statement
*
* As with EXPLAIN, transform the contained statement now.
*/
--- 2123,2130 ----
/*
* transformCreateTableAsStmt -
! * transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW
! * Statement
*
* As with EXPLAIN, transform the contained statement now.
*/
***************
*** 2126,2131 **** transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
--- 2133,2156 ----
{
Query *result;
+ /*
+ * Set relkind in IntoClause based on statement relkind. These are
+ * different types, because the parser users the ObjectType enumeration
+ * and the executor uses RELKIND_* defines.
+ */
+ switch (stmt->relkind)
+ {
+ case (OBJECT_TABLE):
+ stmt->into->relkind = RELKIND_RELATION;
+ break;
+ case (OBJECT_MATVIEW):
+ stmt->into->relkind = RELKIND_MATVIEW;
+ break;
+ default:
+ elog(ERROR, "unrecognized object relkind: %d",
+ (int) stmt->relkind);
+ }
+
/* transform contained query */
stmt->query = (Node *) transformStmt(pstate, stmt->query);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 121,126 **** typedef struct PrivTarget
--- 121,133 ----
#define CAS_NOT_VALID 0x10
#define CAS_NO_INHERIT 0x20
+ /*
+ * In the IntoClause structure there is a char value which will eventually be
+ * set to RELKIND_RELATION or RELKIND_MATVIEW based on the relkind field in
+ * the statement-level structure, which is an ObjectType. Define the default
+ * here, which should always be overridden later.
+ */
+ #define INTO_CLAUSE_RELKIND_DEFAULT '\0'
#define parser_yyerror(msg) scanner_yyerror(msg, yyscanner)
#define parser_errposition(pos) scanner_errposition(pos, yyscanner)
***************
*** 247,252 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 254,260 ----
DeallocateStmt PrepareStmt ExecuteStmt
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
+ CreateMatViewStmt LoadMatViewStmt
%type <node> select_no_parens select_with_parens select_clause
simple_select values_clause
***************
*** 555,561 **** static void processCASbits(int cas_bits, int location, const char *constrType,
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
! MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
--- 563,569 ----
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
! MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
***************
*** 743,748 **** stmt :
--- 751,757 ----
| CreateForeignTableStmt
| CreateFunctionStmt
| CreateGroupStmt
+ | CreateMatViewStmt
| CreateOpClassStmt
| CreateOpFamilyStmt
| AlterOpFamilyStmt
***************
*** 788,793 **** stmt :
--- 797,803 ----
| IndexStmt
| InsertStmt
| ListenStmt
+ | LoadMatViewStmt
| LoadStmt
| LockStmt
| NotifyStmt
***************
*** 1694,1702 **** DiscardStmt:
/*****************************************************************************
*
! * ALTER [ TABLE | INDEX | SEQUENCE | VIEW ] variations
*
! * Note: we accept all subcommands for each of the four variants, and sort
* out what's really legal at execution time.
*****************************************************************************/
--- 1704,1712 ----
/*****************************************************************************
*
! * ALTER [ TABLE | INDEX | SEQUENCE | VIEW | MATERIALIZED VIEW ] variations
*
! * Note: we accept all subcommands for each of the five variants, and sort
* out what's really legal at execution time.
*****************************************************************************/
***************
*** 1773,1778 **** AlterTableStmt:
--- 1783,1806 ----
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $4;
+ n->cmds = $5;
+ n->relkind = OBJECT_MATVIEW;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $6;
+ n->cmds = $7;
+ n->relkind = OBJECT_MATVIEW;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
alter_table_cmds:
***************
*** 3149,3154 **** CreateAsStmt:
--- 3177,3183 ----
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ctas->query = $6;
ctas->into = $4;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
***************
*** 3167,3172 **** create_as_target:
--- 3196,3202 ----
$$->onCommit = $4;
$$->tableSpaceName = $5;
$$->skipData = false; /* might get changed later */
+ $$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
}
;
***************
*** 3180,3185 **** opt_with_data:
--- 3210,3255 ----
/*****************************************************************************
*
* QUERY :
+ * CREATE MATERIALIZED VIEW relname AS SelectStmt
+ *
+ *****************************************************************************/
+
+ CreateMatViewStmt:
+ CREATE OptTemp MATERIALIZED VIEW create_as_target AS SelectStmt opt_with_data
+ {
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+ ctas->query = $7;
+ ctas->into = $5;
+ ctas->relkind = OBJECT_MATVIEW;
+ ctas->is_select_into = false;
+ /* cram additional flags into the IntoClause */
+ $5->rel->relpersistence = $2;
+ $5->skipData = !($8);
+ $$ = (Node *) ctas;
+ }
+ ;
+
+
+ /*****************************************************************************
+ *
+ * QUERY :
+ * LOAD MATERIALIZED VIEW qualified_name
+ *
+ *****************************************************************************/
+
+ LoadMatViewStmt:
+ LOAD MATERIALIZED VIEW qualified_name
+ {
+ LoadMatViewStmt *n = makeNode(LoadMatViewStmt);
+ n->relation = $4;
+ $$ = (Node *) n;
+ }
+ ;
+
+
+ /*****************************************************************************
+ *
+ * QUERY :
* CREATE SEQUENCE seqname
* ALTER SEQUENCE seqname
*
***************
*** 3694,3699 **** AlterExtensionContentsStmt:
--- 3764,3778 ----
n->objname = $6;
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop MATERIALIZED VIEW any_name
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_MATVIEW;
+ n->objname = $7;
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop FOREIGN TABLE any_name
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
***************
*** 5020,5025 **** DropStmt: DROP drop_type IF_P EXISTS any_name_list opt_drop_behavior
--- 5099,5105 ----
drop_type: TABLE { $$ = OBJECT_TABLE; }
| SEQUENCE { $$ = OBJECT_SEQUENCE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| INDEX { $$ = OBJECT_INDEX; }
| FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; }
| EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; }
***************
*** 5086,5092 **** opt_restart_seqs:
* EXTENSION | ROLE | TEXT SEARCH PARSER |
* TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
* TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
! * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER ] <objname> |
* AGGREGATE <aggname> (arg1, ...) |
* FUNCTION <funcname> (arg1, arg2, ...) |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
--- 5166,5173 ----
* EXTENSION | ROLE | TEXT SEARCH PARSER |
* TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE |
* TEXT SEARCH CONFIGURATION | FOREIGN TABLE |
! * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER |
! * MATERIALIZED VIEW] <objname> |
* AGGREGATE <aggname> (arg1, ...) |
* FUNCTION <funcname> (arg1, arg2, ...) |
* OPERATOR <op> (leftoperand_typ, rightoperand_typ) |
***************
*** 5260,5265 **** comment_type:
--- 5341,5347 ----
| DOMAIN_P { $$ = OBJECT_DOMAIN; }
| TYPE_P { $$ = OBJECT_TYPE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| COLLATION { $$ = OBJECT_COLLATION; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
***************
*** 5361,5366 **** security_label_type:
--- 5443,5449 ----
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| TYPE_P { $$ = OBJECT_TYPE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
;
security_label: Sconst { $$ = $1; }
***************
*** 6903,6908 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 6986,7011 ----
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->subname = NULL;
+ n->newname = $7;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_MATVIEW;
+ n->relation = $6;
+ n->subname = NULL;
+ n->newname = $9;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
| ALTER INDEX qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
***************
*** 7310,7315 **** AlterObjectSchemaStmt:
--- 7413,7436 ----
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->newschema = $7;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER MATERIALIZED VIEW IF_P EXISTS qualified_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_MATVIEW;
+ n->relation = $6;
+ n->newschema = $9;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
| ALTER FOREIGN TABLE relation_expr SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
***************
*** 8464,8469 **** ExplainableStmt:
--- 8585,8592 ----
| DeleteStmt
| DeclareCursorStmt
| CreateAsStmt
+ | CreateMatViewStmt
+ | LoadMatViewStmt
| ExecuteStmt /* by default all are $$=$1 */
;
***************
*** 8548,8553 **** ExecuteStmt: EXECUTE name execute_param_clause
--- 8671,8677 ----
n->params = $8;
ctas->query = (Node *) n;
ctas->into = $4;
+ ctas->relkind = OBJECT_TABLE;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
***************
*** 9094,9099 **** into_clause:
--- 9218,9224 ----
$$->onCommit = ONCOMMIT_NOOP;
$$->tableSpaceName = NULL;
$$->skipData = false;
+ $$->relkind = INTO_CLAUSE_RELKIND_DEFAULT;
}
| /*EMPTY*/
{ $$ = NULL; }
***************
*** 12558,12563 **** unreserved_keyword:
--- 12683,12689 ----
| LOCK_P
| MAPPING
| MATCH
+ | MATERIALIZED
| MAXVALUE
| MINUTE_P
| MINVALUE
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 646,651 **** transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
--- 646,652 ----
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_VIEW &&
+ relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
*** a/src/backend/postmaster/autovacuum.c
--- b/src/backend/postmaster/autovacuum.c
***************
*** 1962,1983 **** do_autovacuum(void)
* Scan pg_class to determine which tables to vacuum.
*
* We do this in two passes: on the first one we collect the list of plain
! * relations, and on the second one we collect TOAST tables. The reason
! * for doing the second pass is that during it we want to use the main
! * relation's pg_class.reloptions entry if the TOAST table does not have
! * any, and we cannot obtain it unless we know beforehand what's the main
! * table OID.
*
* We need to check TOAST tables separately because in cases with short,
* wide tables there might be proportionally much more activity in the
* TOAST table than in its parent.
*/
! ScanKeyInit(&key,
! Anum_pg_class_relkind,
! BTEqualStrategyNumber, F_CHAREQ,
! CharGetDatum(RELKIND_RELATION));
!
! relScan = heap_beginscan(classRel, SnapshotNow, 1, &key);
/*
* On the first pass, we collect main tables to vacuum, and also the main
--- 1962,1978 ----
* Scan pg_class to determine which tables to vacuum.
*
* We do this in two passes: on the first one we collect the list of plain
! * relations and materialized views, and on the second one we collect
! * TOAST tables. The reason for doing the second pass is that during it we
! * want to use the main relation's pg_class.reloptions entry if the TOAST
! * table does not have any, and we cannot obtain it unless we know
! * beforehand what's the main table OID.
*
* We need to check TOAST tables separately because in cases with short,
* wide tables there might be proportionally much more activity in the
* TOAST table than in its parent.
*/
! relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL);
/*
* On the first pass, we collect main tables to vacuum, and also the main
***************
*** 1993,1998 **** do_autovacuum(void)
--- 1988,1997 ----
bool doanalyze;
bool wraparound;
+ if (classForm->relkind != RELKIND_RELATION &&
+ classForm->relkind != RELKIND_MATVIEW)
+ continue;
+
relid = HeapTupleGetOid(tuple);
/* Fetch reloptions and the pgstat entry for this table */
***************
*** 2378,2383 **** extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
--- 2377,2383 ----
AutoVacOpts *av;
Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
+ ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
relopts = extractRelOptions(tup, pg_class_desc, InvalidOid);
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
***************
*** 1561,1566 **** pgstat_initstats(Relation rel)
--- 1561,1567 ----
/* We only count stats for things that have storage */
if (!(relkind == RELKIND_RELATION ||
+ relkind == RELKIND_MATVIEW ||
relkind == RELKIND_INDEX ||
relkind == RELKIND_TOASTVALUE ||
relkind == RELKIND_SEQUENCE))
*** a/src/backend/rewrite/rewriteDefine.c
--- b/src/backend/rewrite/rewriteDefine.c
***************
*** 50,56 **** static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
* takes the arguments and inserts them as a row into the system
* relation "pg_rewrite"
*/
! static Oid
InsertRule(char *rulname,
int evtype,
Oid eventrel_oid,
--- 50,56 ----
* takes the arguments and inserts them as a row into the system
* relation "pg_rewrite"
*/
! static void
InsertRule(char *rulname,
int evtype,
Oid eventrel_oid,
***************
*** 183,190 **** InsertRule(char *rulname,
RewriteRelationId, rewriteObjectId, 0, NULL);
heap_close(pg_rewrite_desc, RowExclusiveLock);
-
- return rewriteObjectId;
}
/*
--- 183,188 ----
***************
*** 256,261 **** DefineQueryRewrite(char *rulename,
--- 254,260 ----
* Verify relation is of a type that rules can sensibly be applied to.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 352,358 **** DefineQueryRewrite(char *rulename,
*/
checkRuleResultList(query->targetList,
RelationGetDescr(event_relation),
! true);
/*
* ... there must not be another ON SELECT rule already ...
--- 351,358 ----
*/
checkRuleResultList(query->targetList,
RelationGetDescr(event_relation),
! event_relation->rd_rel->relkind !=
! RELKIND_MATVIEW);
/*
* ... there must not be another ON SELECT rule already ...
***************
*** 410,416 **** DefineQueryRewrite(char *rulename,
* business of converting relations to views is just a kluge to allow
* loading ancient pg_dump files.)
*/
! if (event_relation->rd_rel->relkind != RELKIND_VIEW)
{
HeapScanDesc scanDesc;
--- 410,417 ----
* business of converting relations to views is just a kluge to allow
* loading ancient pg_dump files.)
*/
! if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
! event_relation->rd_rel->relkind != RELKIND_MATVIEW)
{
HeapScanDesc scanDesc;
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1168,1174 **** rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
const char *attrname;
TargetEntry *tle;
! if (target_relation->rd_rel->relkind == RELKIND_RELATION)
{
/*
* Emit CTID so that executor can find the row to update or delete.
--- 1168,1175 ----
const char *attrname;
TargetEntry *tle;
! if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
! target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
* Emit CTID so that executor can find the row to update or delete.
***************
*** 1591,1596 **** fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
--- 1592,1607 ----
rel = heap_open(rte->relid, NoLock);
/*
+ * Skip materialized view expansion when resultRelation is set.
+ */
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
+ rel->rd_rel->relisvalid)
+ {
+ heap_close(rel, NoLock);
+ break;
+ }
+
+ /*
* Collect the RIR rules that we must apply
*/
rules = rel->rd_rules;
*** a/src/backend/storage/lmgr/predicate.c
--- b/src/backend/storage/lmgr/predicate.c
***************
*** 460,472 **** static void OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *read
/*
* Does this relation participate in predicate locking? Temporary and system
! * relations are exempt.
*/
static inline bool
PredicateLockingNeededForRelation(Relation relation)
{
return !(relation->rd_id < FirstBootstrapObjectId ||
! RelationUsesLocalBuffers(relation));
}
/*
--- 460,473 ----
/*
* Does this relation participate in predicate locking? Temporary and system
! * relations are exempt, as are materialized views.
*/
static inline bool
PredicateLockingNeededForRelation(Relation relation)
{
return !(relation->rd_id < FirstBootstrapObjectId ||
! RelationUsesLocalBuffers(relation) ||
! relation->rd_rel->relkind == RELKIND_MATVIEW);
}
/*
*** a/src/backend/tcop/dest.c
--- b/src/backend/tcop/dest.c
***************
*** 32,37 ****
--- 32,38 ----
#include "access/xact.h"
#include "commands/copy.h"
#include "commands/createas.h"
+ #include "commands/matview.h"
#include "executor/functions.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
***************
*** 125,130 **** CreateDestReceiver(CommandDest dest)
--- 126,134 ----
case DestSQLFunction:
return CreateSQLFunctionDestReceiver();
+
+ case DestTransientRel:
+ return CreateTransientRelDestReceiver(InvalidOid);
}
/* should never get here */
***************
*** 157,162 **** EndCommand(const char *commandTag, CommandDest dest)
--- 161,167 ----
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
+ case DestTransientRel:
break;
}
}
***************
*** 198,203 **** NullCommand(CommandDest dest)
--- 203,209 ----
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
+ case DestTransientRel:
break;
}
}
***************
*** 241,246 **** ReadyForQuery(CommandDest dest)
--- 247,253 ----
case DestIntoRel:
case DestCopyOut:
case DestSQLFunction:
+ case DestTransientRel:
break;
}
}
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 37,42 ****
--- 37,43 ----
#include "commands/event_trigger.h"
#include "commands/explain.h"
#include "commands/extension.h"
+ #include "commands/matview.h"
#include "commands/lockcmds.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
***************
*** 110,115 **** CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
--- 111,137 ----
ReleaseSysCache(tuple);
}
+ /*
+ * Tells whether a relation is flagged as valid. The caller must be holding a
+ * lock on the relation.
+ */
+ bool
+ RelationIsFlaggedAsValid(Oid relid)
+ {
+ HeapTuple tuple;
+ Form_pg_class classtup;
+ bool result;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ return false; /* concurrently dropped */
+ classtup = (Form_pg_class) GETSTRUCT(tuple);
+ result = classtup->relisvalid;
+ ReleaseSysCache(tuple);
+
+ return result;
+ }
+
/*
* CommandIsReadOnly: is an executable query read-only?
***************
*** 202,207 **** check_xact_readonly(Node *parsetree)
--- 224,230 ----
case T_CreateSeqStmt:
case T_CreateStmt:
case T_CreateTableAsStmt:
+ case T_LoadMatViewStmt:
case T_CreateTableSpaceStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
***************
*** 682,687 **** standard_ProcessUtility(Node *parsetree,
--- 705,711 ----
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
RemoveRelations((DropStmt *) parsetree);
break;
***************
*** 1144,1149 **** standard_ProcessUtility(Node *parsetree,
--- 1168,1180 ----
queryString, params, completionTag);
break;
+ case T_LoadMatViewStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+ ExecLoadMatView((LoadMatViewStmt *) parsetree,
+ queryString, params, completionTag);
+ break;
+
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree);
break;
***************
*** 1270,1275 **** standard_ProcessUtility(Node *parsetree,
--- 1301,1307 ----
ReindexIndex(stmt->relation);
break;
case OBJECT_TABLE:
+ case OBJECT_MATVIEW:
ReindexTable(stmt->relation);
break;
case OBJECT_DATABASE:
***************
*** 1489,1497 **** QueryReturnsTuples(Query *parsetree)
* We assume it is invoked only on already-parse-analyzed statements
* (else the contained parsetree isn't a Query yet).
*
! * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO),
! * potentially Query-containing utility statements can be nested. This
! * function will drill down to a non-utility Query, or return NULL if none.
*/
Query *
UtilityContainsQuery(Node *parsetree)
--- 1521,1530 ----
* We assume it is invoked only on already-parse-analyzed statements
* (else the contained parsetree isn't a Query yet).
*
! * In some cases (currently, only EXPLAIN of CREATE TABLE AS/SELECT INTO and
! * CREATE MATERIALIZED VIEW), potentially Query-containing utility statements
! * can be nested. This function will drill down to a non-utility Query, or
! * return NULL if none.
*/
Query *
UtilityContainsQuery(Node *parsetree)
***************
*** 1635,1640 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1668,1676 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "ALTER MATERIALIZED VIEW";
+ break;
default:
tag = "???";
break;
***************
*** 1832,1837 **** CreateCommandTag(Node *parsetree)
--- 1868,1876 ----
case OBJECT_VIEW:
tag = "DROP VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "DROP MATERIALIZED VIEW";
+ break;
case OBJECT_INDEX:
tag = "DROP INDEX";
break;
***************
*** 2093,2102 **** CreateCommandTag(Node *parsetree)
break;
case T_CreateTableAsStmt:
! if (((CreateTableAsStmt *) parsetree)->is_select_into)
! tag = "SELECT INTO";
! else
! tag = "CREATE TABLE AS";
break;
case T_VariableSetStmt:
--- 2132,2155 ----
break;
case T_CreateTableAsStmt:
! switch (((CreateTableAsStmt *) parsetree)->relkind)
! {
! case OBJECT_TABLE:
! if (((CreateTableAsStmt *) parsetree)->is_select_into)
! tag = "SELECT INTO";
! else
! tag = "CREATE TABLE AS";
! break;
! case OBJECT_MATVIEW:
! tag = "CREATE MATERIALIZED VIEW";
! break;
! default:
! tag = "???";
! }
! break;
!
! case T_LoadMatViewStmt:
! tag = "LOAD MATERIALIZED VIEW";
break;
case T_VariableSetStmt:
***************
*** 2629,2634 **** GetCommandLogLevel(Node *parsetree)
--- 2682,2691 ----
lev = LOGSTMT_DDL;
break;
+ case T_LoadMatViewStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
*** a/src/backend/utils/adt/dbsize.c
--- b/src/backend/utils/adt/dbsize.c
***************
*** 715,720 **** pg_relation_filenode(PG_FUNCTION_ARGS)
--- 715,721 ----
switch (relform->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
case RELKIND_INDEX:
case RELKIND_SEQUENCE:
case RELKIND_TOASTVALUE:
***************
*** 763,768 **** pg_relation_filepath(PG_FUNCTION_ARGS)
--- 764,770 ----
switch (relform->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
case RELKIND_INDEX:
case RELKIND_SEQUENCE:
case RELKIND_TOASTVALUE:
*** a/src/backend/utils/adt/xml.c
--- b/src/backend/utils/adt/xml.c
***************
*** 2285,2291 **** schema_get_xml_visible_tables(Oid nspid)
StringInfoData query;
initStringInfo(&query);
! appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'v') AND pg_catalog.has_table_privilege (oid, 'SELECT') ORDER BY relname;", nspid);
return query_to_oid_list(query.data);
}
--- 2285,2291 ----
StringInfoData query;
initStringInfo(&query);
! appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class WHERE relnamespace = %u AND relkind IN ('r', 'm', 'v') AND pg_catalog.has_table_privilege (oid, 'SELECT') ORDER BY relname;", nspid);
return query_to_oid_list(query.data);
}
***************
*** 2311,2317 **** static List *
database_get_xml_visible_tables(void)
{
/* At the moment there is no order required here. */
! return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'v') AND pg_catalog.has_table_privilege (pg_class.oid, 'SELECT') AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
}
--- 2311,2317 ----
database_get_xml_visible_tables(void)
{
/* At the moment there is no order required here. */
! return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class WHERE relkind IN ('r', 'm', 'v') AND pg_catalog.has_table_privilege (pg_class.oid, 'SELECT') AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
}
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 376,381 **** RelationParseRelOptions(Relation relation, HeapTuple tuple)
--- 376,382 ----
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
break;
default:
return;
***************
*** 1418,1423 **** formrdesc(const char *relationName, Oid relationReltype,
--- 1419,1425 ----
relation->rd_rel->reltuples = 0;
relation->rd_rel->relallvisible = 0;
relation->rd_rel->relkind = RELKIND_RELATION;
+ relation->rd_rel->relisvalid = true;
relation->rd_rel->relhasoids = hasoids;
relation->rd_rel->relnatts = (int16) natts;
***************
*** 2521,2526 **** RelationBuildLocalRelation(const char *relname,
--- 2523,2529 ----
rel->rd_rel->relhasoids = rel->rd_att->tdhasoid;
rel->rd_rel->relnatts = natts;
rel->rd_rel->reltype = InvalidOid;
+ rel->rd_rel->relisvalid = true;
/* needed when bootstrapping: */
rel->rd_rel->relowner = BOOTSTRAP_SUPERUSERID;
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
***************
*** 2030,2036 **** setup_privileges(void)
static char *privileges_setup[] = {
"UPDATE pg_class "
" SET relacl = E'{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
! " WHERE relkind IN ('r', 'v', 'S') AND relacl IS NULL;\n",
"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
"REVOKE ALL ON pg_largeobject FROM PUBLIC;\n",
--- 2030,2036 ----
static char *privileges_setup[] = {
"UPDATE pg_class "
" SET relacl = E'{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
! " WHERE relkind IN ('r', 'v', 'm', 'S') AND relacl IS NULL;\n",
"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
"REVOKE ALL ON pg_largeobject FROM PUBLIC;\n",
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 272,278 **** flagInhTables(TableInfo *tblinfo, int numTables,
{
/* Sequences and views never have parents */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
! tblinfo[i].relkind == RELKIND_VIEW)
continue;
/* Don't bother computing anything for non-target tables, either */
--- 272,279 ----
{
/* Sequences and views never have parents */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
! tblinfo[i].relkind == RELKIND_VIEW ||
! tblinfo[i].relkind == RELKIND_MATVIEW)
continue;
/* Don't bother computing anything for non-target tables, either */
***************
*** 317,323 **** flagInhAttrs(TableInfo *tblinfo, int numTables)
/* Sequences and views never have parents */
if (tbinfo->relkind == RELKIND_SEQUENCE ||
! tbinfo->relkind == RELKIND_VIEW)
continue;
/* Don't bother computing anything for non-target tables, either */
--- 318,325 ----
/* Sequences and views never have parents */
if (tbinfo->relkind == RELKIND_SEQUENCE ||
! tbinfo->relkind == RELKIND_VIEW ||
! tbinfo->relkind == RELKIND_MATVIEW)
continue;
/* Don't bother computing anything for non-target tables, either */
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2909,2915 **** _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
const char *type = te->desc;
/* Use ALTER TABLE for views and sequences */
! if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0)
type = "TABLE";
/* objects named by a schema and name */
--- 2909,2916 ----
const char *type = te->desc;
/* Use ALTER TABLE for views and sequences */
! if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0||
! strcmp(type, "MATERIALIZED VIEW") == 0)
type = "TABLE";
/* objects named by a schema and name */
***************
*** 3141,3146 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 3142,3148 ----
strcmp(te->desc, "TABLE") == 0 ||
strcmp(te->desc, "TYPE") == 0 ||
strcmp(te->desc, "VIEW") == 0 ||
+ strcmp(te->desc, "MATERIALIZED VIEW") == 0 ||
strcmp(te->desc, "SEQUENCE") == 0 ||
strcmp(te->desc, "FOREIGN TABLE") == 0 ||
strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 194,199 **** static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo);
--- 194,200 ----
static void dumpSequence(Archive *fout, TableInfo *tbinfo);
static void dumpSequenceData(Archive *fout, TableDataInfo *tdinfo);
static void dumpIndex(Archive *fout, IndxInfo *indxinfo);
+ static void dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo);
static void dumpConstraint(Archive *fout, ConstraintInfo *coninfo);
static void dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo);
static void dumpTSParser(Archive *fout, TSParserInfo *prsinfo);
***************
*** 1042,1050 **** expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! "\nWHERE c.relkind in ('%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
! RELKIND_FOREIGN_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
--- 1043,1051 ----
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
! "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
! RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
***************
*** 1605,1610 **** dumpTableData(Archive *fout, TableDataInfo *tdinfo)
--- 1606,1652 ----
}
/*
+ * loadMatViewData -
+ * load the contents of a single materialized view
+ *
+ * Actually, this just makes an ArchiveEntry for the LOAD MATERIALIZED VIEW
+ * statement.
+ */
+ static void
+ loadMatViewData(Archive *fout, TableDataInfo *tdinfo)
+ {
+ TableInfo *tbinfo = tdinfo->tdtable;
+ PQExpBuffer q;
+
+ if (!(tbinfo->relisvalid))
+ return;
+
+ q = createPQExpBuffer();
+ appendPQExpBuffer(q, "LOAD MATERIALIZED VIEW %s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ ArchiveEntry(fout,
+ tdinfo->dobj.catId, /* catalog ID */
+ tdinfo->dobj.dumpId, /* dump ID */
+ tbinfo->dobj.name, /* Name */
+ tbinfo->dobj.namespace->dobj.name, /* Namespace */
+ NULL, /* Tablespace */
+ tbinfo->rolname, /* Owner */
+ false, /* with oids */
+ "MATERIALIZED VIEW DATA", /* Desc */
+ SECTION_POST_DATA, /* Section */
+ q->data, /* Create */
+ "", /* Del */
+ NULL, /* Copy */
+ &(tbinfo->dobj.dumpId), /* Deps */
+ 1, /* # Deps */
+ NULL, /* Dumper */
+ NULL); /* Dumper Arg */
+
+ destroyPQExpBuffer(q);
+ }
+
+ /*
* getTableData -
* set up dumpable objects representing the contents of tables
*/
***************
*** 1658,1664 **** makeTableDataInfo(TableInfo *tbinfo, bool oids)
/* OK, let's dump it */
tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
! tdinfo->dobj.objType = DO_TABLE_DATA;
/*
* Note: use tableoid 0 so that this object won't be mistaken for
--- 1700,1709 ----
/* OK, let's dump it */
tdinfo = (TableDataInfo *) pg_malloc(sizeof(TableDataInfo));
! if (tbinfo->relkind == RELKIND_MATVIEW)
! tdinfo->dobj.objType = DO_LOAD_MATVIEW;
! else
! tdinfo->dobj.objType = DO_TABLE_DATA;
/*
* Note: use tableoid 0 so that this object won't be mistaken for
***************
*** 3902,3907 **** getTables(Archive *fout, int *numTables)
--- 3947,3953 ----
int i_toastoid;
int i_toastfrozenxid;
int i_relpersistence;
+ int i_relisvalid;
int i_owning_tab;
int i_owning_col;
int i_reltablespace;
***************
*** 3946,3952 **** getTables(Archive *fout, int *numTables)
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "c.relpersistence, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 3992,3998 ----
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "c.relpersistence, c.relisvalid, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 3960,3972 **** getTables(Archive *fout, int *numTables)
"d.objsubid = 0 AND "
"d.refclassid = c.tableoid AND d.deptype = 'a') "
"LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
! "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
username_subquery,
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
! RELKIND_FOREIGN_TABLE);
}
else if (fout->remoteVersion >= 90000)
{
--- 4006,4018 ----
"d.objsubid = 0 AND "
"d.refclassid = c.tableoid AND d.deptype = 'a') "
"LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
! "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
username_subquery,
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
! RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
}
else if (fout->remoteVersion >= 90000)
{
***************
*** 3982,3988 **** getTables(Archive *fout, int *numTables)
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 4028,4034 ----
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 4017,4023 **** getTables(Archive *fout, int *numTables)
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 4063,4069 ----
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 4052,4058 **** getTables(Archive *fout, int *numTables)
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 4098,4104 ----
"c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 4088,4094 **** getTables(Archive *fout, int *numTables)
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 4134,4140 ----
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 4123,4129 **** getTables(Archive *fout, int *numTables)
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
--- 4169,4175 ----
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"d.refobjid AS owning_tab, "
"d.refobjsubid AS owning_col, "
***************
*** 4154,4160 **** getTables(Archive *fout, int *numTables)
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
--- 4200,4206 ----
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
***************
*** 4180,4186 **** getTables(Archive *fout, int *numTables)
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
--- 4226,4232 ----
"0 AS relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
***************
*** 4216,4222 **** getTables(Archive *fout, int *numTables)
"0 as relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
--- 4262,4268 ----
"0 as relfrozenxid, "
"0 AS toid, "
"0 AS tfrozenxid, "
! "'p' AS relpersistence, 't'::bool as relisvalid, "
"NULL AS reloftype, "
"NULL::oid AS owning_tab, "
"NULL::int4 AS owning_col, "
***************
*** 4264,4269 **** getTables(Archive *fout, int *numTables)
--- 4310,4316 ----
i_toastoid = PQfnumber(res, "toid");
i_toastfrozenxid = PQfnumber(res, "tfrozenxid");
i_relpersistence = PQfnumber(res, "relpersistence");
+ i_relisvalid = PQfnumber(res, "relisvalid");
i_owning_tab = PQfnumber(res, "owning_tab");
i_owning_col = PQfnumber(res, "owning_col");
i_reltablespace = PQfnumber(res, "reltablespace");
***************
*** 4305,4310 **** getTables(Archive *fout, int *numTables)
--- 4352,4358 ----
tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
+ tblinfo[i].relisvalid = (strcmp(PQgetvalue(res, i, i_relisvalid), "t") == 0);
tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
tblinfo[i].toast_oid = atooid(PQgetvalue(res, i, i_toastoid));
tblinfo[i].toast_frozenxid = atooid(PQgetvalue(res, i, i_toastfrozenxid));
***************
*** 4500,4507 **** getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{
TableInfo *tbinfo = &tblinfo[i];
! /* Only plain tables have indexes */
! if (tbinfo->relkind != RELKIND_RELATION || !tbinfo->hasindex)
continue;
/* Ignore indexes of tables not to be dumped */
--- 4548,4558 ----
{
TableInfo *tbinfo = &tblinfo[i];
! /* Only plain tables and materialized views have indexes. */
! if (tbinfo->relkind != RELKIND_RELATION &&
! tbinfo->relkind != RELKIND_MATVIEW)
! continue;
! if (!tbinfo->hasindex)
continue;
/* Ignore indexes of tables not to be dumped */
***************
*** 4715,4721 **** getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{
char contype;
! indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
AssignDumpId(&indxinfo[j].dobj);
--- 4766,4775 ----
{
char contype;
! if (tbinfo->relkind == RELKIND_MATVIEW)
! indxinfo[j].dobj.objType = DO_MATVIEW_INDEX;
! else
! indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
AssignDumpId(&indxinfo[j].dobj);
***************
*** 5083,5094 **** getRules(Archive *fout, int *numRules)
if (ruleinfo[i].ruletable)
{
/*
! * If the table is a view, force its ON SELECT rule to be sorted
! * before the view itself --- this ensures that any dependencies
! * for the rule affect the table's positioning. Other rules are
! * forced to appear after their table.
*/
! if (ruleinfo[i].ruletable->relkind == RELKIND_VIEW &&
ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead)
{
addObjectDependency(&ruleinfo[i].ruletable->dobj,
--- 5137,5150 ----
if (ruleinfo[i].ruletable)
{
/*
! * If the table is a view or materialized view, force its ON
! * SELECT rule to be sorted before the view itself --- this
! * ensures that any dependencies for the rule affect the table's
! * positioning. Other rules are forced to appear after their
! * table.
*/
! if ((ruleinfo[i].ruletable->relkind == RELKIND_VIEW ||
! ruleinfo[i].ruletable->relkind == RELKIND_MATVIEW) &&
ruleinfo[i].ev_type == '1' && ruleinfo[i].is_instead)
{
addObjectDependency(&ruleinfo[i].ruletable->dobj,
***************
*** 7294,7299 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7350,7361 ----
case DO_INDEX:
dumpIndex(fout, (IndxInfo *) dobj);
break;
+ case DO_LOAD_MATVIEW:
+ loadMatViewData(fout, (TableDataInfo *) dobj);
+ break;
+ case DO_MATVIEW_INDEX:
+ dumpMatViewIndex(fout, (IndxInfo *) dobj);
+ break;
case DO_RULE:
dumpRule(fout, (RuleInfo *) dobj);
break;
***************
*** 12290,12306 **** dumpTable(Archive *fout, TableInfo *tbinfo)
}
/*
* dumpTableSchema
* write the declaration (not data) of one user-defined table or view
*/
static void
dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
- PQExpBuffer query = createPQExpBuffer();
PQExpBuffer q = createPQExpBuffer();
PQExpBuffer delq = createPQExpBuffer();
PQExpBuffer labelq = createPQExpBuffer();
- PGresult *res;
int numParents;
TableInfo **parents;
int actual_atts; /* number of attrs in this CREATE statement */
--- 12352,12423 ----
}
/*
+ * Create the AS clause for a view or materialized view. The semicolon is
+ * stripped because a materialized view must add a WITH NO DATA clause.
+ *
+ * This returns a new buffer which must be freed by the caller.
+ */
+ static PQExpBuffer
+ createViewAsClause(Archive *fout, TableInfo *tbinfo)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer result = createPQExpBuffer();
+ PGresult *res;
+ int len;
+
+ /* Fetch the view definition */
+ if (fout->remoteVersion >= 70300)
+ {
+ /* Beginning in 7.3, viewname is not unique; rely on OID */
+ appendPQExpBuffer(query,
+ "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
+ tbinfo->dobj.catId.oid);
+ }
+ else
+ {
+ appendPQExpBuffer(query, "SELECT definition AS viewdef "
+ "FROM pg_views WHERE viewname = ");
+ appendStringLiteralAH(query, tbinfo->dobj.name, fout);
+ appendPQExpBuffer(query, ";");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ if (PQntuples(res) < 1)
+ exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n",
+ tbinfo->dobj.name);
+ else
+ exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
+ tbinfo->dobj.name);
+ }
+
+ len = PQgetlength(res, 0, 0);
+
+ if (len == 0)
+ exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
+ tbinfo->dobj.name);
+
+ /* Strip off the trailing semicolon so that other things may follow. */
+ appendBinaryPQExpBuffer(result, PQgetvalue(res, 0, 0), len - 1);
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ return result;
+ }
+
+ /*
* dumpTableSchema
* write the declaration (not data) of one user-defined table or view
*/
static void
dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
PQExpBuffer q = createPQExpBuffer();
PQExpBuffer delq = createPQExpBuffer();
PQExpBuffer labelq = createPQExpBuffer();
int numParents;
TableInfo **parents;
int actual_atts; /* number of attrs in this CREATE statement */
***************
*** 12321,12364 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
/* Is it a table or a view? */
if (tbinfo->relkind == RELKIND_VIEW)
{
! char *viewdef;
reltypename = "VIEW";
- /* Fetch the view definition */
- if (fout->remoteVersion >= 70300)
- {
- /* Beginning in 7.3, viewname is not unique; rely on OID */
- appendPQExpBuffer(query,
- "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
- tbinfo->dobj.catId.oid);
- }
- else
- {
- appendPQExpBuffer(query, "SELECT definition AS viewdef "
- "FROM pg_views WHERE viewname = ");
- appendStringLiteralAH(query, tbinfo->dobj.name, fout);
- appendPQExpBuffer(query, ";");
- }
-
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
-
- if (PQntuples(res) != 1)
- {
- if (PQntuples(res) < 1)
- exit_horribly(NULL, "query to obtain definition of view \"%s\" returned no data\n",
- tbinfo->dobj.name);
- else
- exit_horribly(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
- tbinfo->dobj.name);
- }
-
- viewdef = PQgetvalue(res, 0, 0);
-
- if (strlen(viewdef) == 0)
- exit_horribly(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
- tbinfo->dobj.name);
-
/*
* DROP must be fully qualified in case same name appears in
* pg_catalog
--- 12438,12447 ----
/* Is it a table or a view? */
if (tbinfo->relkind == RELKIND_VIEW)
{
! PQExpBuffer result;
reltypename = "VIEW";
/*
* DROP must be fully qualified in case same name appears in
* pg_catalog
***************
*** 12375,12423 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
! appendPQExpBuffer(q, " AS\n %s\n", viewdef);
appendPQExpBuffer(labelq, "VIEW %s",
fmtId(tbinfo->dobj.name));
-
- PQclear(res);
}
else
{
! if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
{
! int i_srvname;
! int i_ftoptions;
!
! reltypename = "FOREIGN TABLE";
!
! /* retrieve name of foreign server and generic options */
! appendPQExpBuffer(query,
! "SELECT fs.srvname, "
! "pg_catalog.array_to_string(ARRAY("
! "SELECT pg_catalog.quote_ident(option_name) || "
! "' ' || pg_catalog.quote_literal(option_value) "
! "FROM pg_catalog.pg_options_to_table(ftoptions) "
! "ORDER BY option_name"
! "), E',\n ') AS ftoptions "
! "FROM pg_catalog.pg_foreign_table ft "
! "JOIN pg_catalog.pg_foreign_server fs "
! "ON (fs.oid = ft.ftserver) "
! "WHERE ft.ftrelid = '%u'",
! tbinfo->dobj.catId.oid);
! res = ExecuteSqlQueryForSingleRow(fout, query->data);
! i_srvname = PQfnumber(res, "srvname");
! i_ftoptions = PQfnumber(res, "ftoptions");
! srvname = pg_strdup(PQgetvalue(res, 0, i_srvname));
! ftoptions = pg_strdup(PQgetvalue(res, 0, i_ftoptions));
! PQclear(res);
! }
! else
! {
! reltypename = "TABLE";
! srvname = NULL;
! ftoptions = NULL;
}
numParents = tbinfo->numParents;
parents = tbinfo->parents;
--- 12458,12517 ----
appendPQExpBuffer(q, "CREATE VIEW %s", fmtId(tbinfo->dobj.name));
if (tbinfo->reloptions && strlen(tbinfo->reloptions) > 0)
appendPQExpBuffer(q, " WITH (%s)", tbinfo->reloptions);
! result = createViewAsClause(fout, tbinfo);
! appendPQExpBuffer(q, " AS\n %s;\n", result->data);
! destroyPQExpBuffer(result);
appendPQExpBuffer(labelq, "VIEW %s",
fmtId(tbinfo->dobj.name));
}
else
{
! switch (tbinfo->relkind)
{
! case (RELKIND_FOREIGN_TABLE):
! {
! PQExpBuffer query = createPQExpBuffer();
! PGresult *res;
! int i_srvname;
! int i_ftoptions;
!
! reltypename = "FOREIGN TABLE";
!
! /* retrieve name of foreign server and generic options */
! appendPQExpBuffer(query,
! "SELECT fs.srvname, "
! "pg_catalog.array_to_string(ARRAY("
! "SELECT pg_catalog.quote_ident(option_name) || "
! "' ' || pg_catalog.quote_literal(option_value) "
! "FROM pg_catalog.pg_options_to_table(ftoptions) "
! "ORDER BY option_name"
! "), E',\n ') AS ftoptions "
! "FROM pg_catalog.pg_foreign_table ft "
! "JOIN pg_catalog.pg_foreign_server fs "
! "ON (fs.oid = ft.ftserver) "
! "WHERE ft.ftrelid = '%u'",
! tbinfo->dobj.catId.oid);
! res = ExecuteSqlQueryForSingleRow(fout, query->data);
! i_srvname = PQfnumber(res, "srvname");
! i_ftoptions = PQfnumber(res, "ftoptions");
! srvname = pg_strdup(PQgetvalue(res, 0, i_srvname));
! ftoptions = pg_strdup(PQgetvalue(res, 0, i_ftoptions));
! PQclear(res);
! destroyPQExpBuffer(query);
! break;
! }
! case (RELKIND_MATVIEW):
! reltypename = "MATERIALIZED VIEW";
! srvname = NULL;
! ftoptions = NULL;
! break;
! default:
! reltypename = "TABLE";
! srvname = NULL;
! ftoptions = NULL;
}
+
numParents = tbinfo->numParents;
parents = tbinfo->parents;
***************
*** 12489,12497 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
actual_atts++;
/* Attribute name */
! appendPQExpBuffer(q, "%s ",
fmtId(tbinfo->attnames[j]));
if (tbinfo->attisdropped[j])
{
/*
--- 12583,12595 ----
actual_atts++;
/* Attribute name */
! appendPQExpBuffer(q, "%s",
fmtId(tbinfo->attnames[j]));
+ /* Materialized views just have column names, not types. */
+ if (tbinfo->relkind == RELKIND_MATVIEW)
+ continue;
+
if (tbinfo->attisdropped[j])
{
/*
***************
*** 12499,12505 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* so we will not have gotten a valid type name; insert
* INTEGER as a stopgap. We'll clean things up later.
*/
! appendPQExpBuffer(q, "INTEGER /* dummy */");
/* Skip all the rest, too */
continue;
}
--- 12597,12603 ----
* so we will not have gotten a valid type name; insert
* INTEGER as a stopgap. We'll clean things up later.
*/
! appendPQExpBuffer(q, " INTEGER /* dummy */");
/* Skip all the rest, too */
continue;
}
***************
*** 12507,12523 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
/* Attribute type */
if (tbinfo->reloftype && !binary_upgrade)
{
! appendPQExpBuffer(q, "WITH OPTIONS");
}
else if (fout->remoteVersion >= 70100)
{
! appendPQExpBuffer(q, "%s",
tbinfo->atttypnames[j]);
}
else
{
/* If no format_type, fake it */
! appendPQExpBuffer(q, "%s",
myFormatType(tbinfo->atttypnames[j],
tbinfo->atttypmod[j]));
}
--- 12605,12621 ----
/* Attribute type */
if (tbinfo->reloftype && !binary_upgrade)
{
! appendPQExpBuffer(q, " WITH OPTIONS");
}
else if (fout->remoteVersion >= 70100)
{
! appendPQExpBuffer(q, " %s",
tbinfo->atttypnames[j]);
}
else
{
/* If no format_type, fake it */
! appendPQExpBuffer(q, " %s",
myFormatType(tbinfo->atttypnames[j],
tbinfo->atttypmod[j]));
}
***************
*** 12624,12630 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (ftoptions && ftoptions[0])
appendPQExpBuffer(q, "\nOPTIONS (\n %s\n)", ftoptions);
! appendPQExpBuffer(q, ";\n");
/*
* To create binary-compatible heap files, we have to ensure the same
--- 12722,12741 ----
if (ftoptions && ftoptions[0])
appendPQExpBuffer(q, "\nOPTIONS (\n %s\n)", ftoptions);
! /*
! * For materialized views, create the AS clause just like a view.
! */
! if (tbinfo->relkind == RELKIND_MATVIEW)
! {
! PQExpBuffer result;
!
! result = createViewAsClause(fout, tbinfo);
! appendPQExpBuffer(q, " AS\n %s\n WITH NO DATA;\n",
! result->data);
! destroyPQExpBuffer(result);
! }
! else
! appendPQExpBuffer(q, ";\n");
/*
* To create binary-compatible heap files, we have to ensure the same
***************
*** 12880,12886 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
dumpTableConstraintComment(fout, constr);
}
- destroyPQExpBuffer(query);
destroyPQExpBuffer(q);
destroyPQExpBuffer(delq);
destroyPQExpBuffer(labelq);
--- 12991,12996 ----
***************
*** 13049,13054 **** dumpIndex(Archive *fout, IndxInfo *indxinfo)
--- 13159,13240 ----
}
/*
+ * dumpMatViewIndex
+ * write out to fout a user-defined index
+ */
+ static void
+ dumpMatViewIndex(Archive *fout, IndxInfo *indxinfo)
+ {
+ TableInfo *tbinfo = indxinfo->indextable;
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+
+ if (dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ appendPQExpBuffer(labelq, "INDEX %s",
+ fmtId(indxinfo->dobj.name));
+
+ /*
+ * If there's an associated constraint, don't dump the index per se, but
+ * do dump any comment for it. (This is safe because dependency ordering
+ * will have ensured the constraint is emitted first.)
+ */
+ if (indxinfo->indexconstraint == 0)
+ {
+ if (binary_upgrade)
+ binary_upgrade_set_pg_class_oids(fout, q,
+ indxinfo->dobj.catId.oid, true);
+
+ /* Plain secondary index */
+ appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef);
+
+ /* If the index is clustered, we need to record that. */
+ if (indxinfo->indisclustered)
+ {
+ appendPQExpBuffer(q, "\nALTER TABLE %s CLUSTER",
+ fmtId(tbinfo->dobj.name));
+ appendPQExpBuffer(q, " ON %s;\n",
+ fmtId(indxinfo->dobj.name));
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP INDEX %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ fmtId(indxinfo->dobj.name));
+
+ ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId,
+ indxinfo->dobj.name,
+ tbinfo->dobj.namespace->dobj.name,
+ indxinfo->tablespace,
+ tbinfo->rolname, false,
+ "INDEX", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+ }
+
+ /* Dump Index Comments */
+ dumpComment(fout, labelq->data,
+ tbinfo->dobj.namespace->dobj.name,
+ tbinfo->rolname,
+ indxinfo->dobj.catId, 0, indxinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ }
+
+ /*
* dumpConstraint
* write out to fout a user-defined constraint
*/
***************
*** 14374,14379 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
--- 14560,14567 ----
addObjectDependency(postDataBound, dobj->dumpId);
break;
case DO_INDEX:
+ case DO_LOAD_MATVIEW:
+ case DO_MATVIEW_INDEX:
case DO_TRIGGER:
case DO_EVENT_TRIGGER:
case DO_DEFAULT_ACL:
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 121,127 **** typedef enum
DO_BLOB_DATA,
DO_PRE_DATA_BOUNDARY,
DO_POST_DATA_BOUNDARY,
! DO_EVENT_TRIGGER
} DumpableObjectType;
typedef struct _dumpableObject
--- 121,129 ----
DO_BLOB_DATA,
DO_PRE_DATA_BOUNDARY,
DO_POST_DATA_BOUNDARY,
! DO_EVENT_TRIGGER,
! DO_LOAD_MATVIEW,
! DO_MATVIEW_INDEX
} DumpableObjectType;
typedef struct _dumpableObject
***************
*** 252,257 **** typedef struct _tableInfo
--- 254,260 ----
bool hasrules; /* does it have any rules? */
bool hastriggers; /* does it have any triggers? */
bool hasoids; /* does it have OIDs? */
+ bool relisvalid; /* is valid for use in queries */
uint32 frozenxid; /* for restore frozen xid */
Oid toast_oid; /* for restore toast frozen xid */
uint32 toast_frozenxid; /* for restore toast frozen xid */
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 69,75 **** static const int oldObjectTypePriority[] =
12, /* DO_BLOB_DATA */
10, /* DO_PRE_DATA_BOUNDARY */
13, /* DO_POST_DATA_BOUNDARY */
! 20 /* DO_EVENT_TRIGGER */
};
/*
--- 69,77 ----
12, /* DO_BLOB_DATA */
10, /* DO_PRE_DATA_BOUNDARY */
13, /* DO_POST_DATA_BOUNDARY */
! 20, /* DO_EVENT_TRIGGER */
! 15, /* DO_LOAD_MATVIEW */
! 15 /* DO_MATVIEW_INDEX */
};
/*
***************
*** 97,106 **** static const int newObjectTypePriority[] =
18, /* DO_TABLE */
20, /* DO_ATTRDEF */
27, /* DO_INDEX */
! 28, /* DO_RULE */
! 29, /* DO_TRIGGER */
26, /* DO_CONSTRAINT */
! 30, /* DO_FK_CONSTRAINT */
2, /* DO_PROCLANG */
10, /* DO_CAST */
23, /* DO_TABLE_DATA */
--- 99,108 ----
18, /* DO_TABLE */
20, /* DO_ATTRDEF */
27, /* DO_INDEX */
! 29, /* DO_RULE */
! 30, /* DO_TRIGGER */
26, /* DO_CONSTRAINT */
! 31, /* DO_FK_CONSTRAINT */
2, /* DO_PROCLANG */
10, /* DO_CAST */
23, /* DO_TABLE_DATA */
***************
*** 111,122 **** static const int newObjectTypePriority[] =
15, /* DO_TSCONFIG */
16, /* DO_FDW */
17, /* DO_FOREIGN_SERVER */
! 31, /* DO_DEFAULT_ACL */
21, /* DO_BLOB */
24, /* DO_BLOB_DATA */
22, /* DO_PRE_DATA_BOUNDARY */
25, /* DO_POST_DATA_BOUNDARY */
! 32 /* DO_EVENT_TRIGGER */
};
static DumpId preDataBoundId;
--- 113,126 ----
15, /* DO_TSCONFIG */
16, /* DO_FDW */
17, /* DO_FOREIGN_SERVER */
! 32, /* DO_DEFAULT_ACL */
21, /* DO_BLOB */
24, /* DO_BLOB_DATA */
22, /* DO_PRE_DATA_BOUNDARY */
25, /* DO_POST_DATA_BOUNDARY */
! 33, /* DO_EVENT_TRIGGER */
! 28, /* DO_LOAD_MATVIEW */
! 28 /* DO_MATVIEW_INDEX */
};
static DumpId preDataBoundId;
***************
*** 1153,1158 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
--- 1157,1172 ----
"INDEX %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
+ case DO_LOAD_MATVIEW:
+ snprintf(buf, bufsize,
+ "LOAD MATERIALIZED VIEW %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
+ case DO_MATVIEW_INDEX:
+ snprintf(buf, bufsize,
+ "MATERIALIZED VIEW INDEX %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
case DO_RULE:
snprintf(buf, bufsize,
"RULE %s (ID %d OID %u)",
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
***************
*** 355,361 **** exec_command(const char *cmd,
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvsE", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
--- 355,361 ----
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvmsE", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
***************
*** 422,427 **** exec_command(const char *cmd,
--- 422,428 ----
break;
case 't':
case 'v':
+ case 'm':
case 'i':
case 's':
case 'E':
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 715,725 **** permissionsList(const char *pattern)
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'S' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
" ",
gettext_noop("Schema"),
gettext_noop("Name"),
! gettext_noop("table"), gettext_noop("view"), gettext_noop("sequence"),
gettext_noop("foreign table"),
gettext_noop("Type"));
--- 715,734 ----
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind"
! " WHEN 'r' THEN '%s'"
! " WHEN 'v' THEN '%s'"
! " WHEN 'm' THEN '%s'"
! " WHEN 'S' THEN '%s'"
! " WHEN 'f' THEN '%s'"
! " END as \"%s\",\n"
" ",
gettext_noop("Schema"),
gettext_noop("Name"),
! gettext_noop("table"),
! gettext_noop("view"),
! gettext_noop("materialized view"),
! gettext_noop("sequence"),
gettext_noop("foreign table"),
gettext_noop("Type"));
***************
*** 736,742 **** permissionsList(const char *pattern)
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! "WHERE c.relkind IN ('r', 'v', 'S', 'f')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
--- 745,751 ----
appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
! "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
***************
*** 1308,1313 **** describeOneTableDetails(const char *schemaname,
--- 1317,1323 ----
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
appendPQExpBuffer(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
***************
*** 1336,1341 **** describeOneTableDetails(const char *schemaname,
--- 1346,1359 ----
printfPQExpBuffer(&title, _("View \"%s.%s\""),
schemaname, relationname);
break;
+ case 'm':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged materialized view \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Materialized view \"%s.%s\""),
+ schemaname, relationname);
+ break;
case 'S':
printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
schemaname, relationname);
***************
*** 1378,1383 **** describeOneTableDetails(const char *schemaname,
--- 1396,1402 ----
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ tableinfo.relkind == 'm' ||
tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
{
show_modifiers = true;
***************
*** 1397,1406 **** describeOneTableDetails(const char *schemaname,
if (verbose)
{
headers[cols++] = gettext_noop("Storage");
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
headers[cols++] = gettext_noop("Description");
}
--- 1416,1427 ----
if (verbose)
{
headers[cols++] = gettext_noop("Storage");
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! tableinfo.relkind == 'f')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ tableinfo.relkind == 'm' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
headers[cols++] = gettext_noop("Description");
}
***************
*** 1411,1418 **** describeOneTableDetails(const char *schemaname,
for (i = 0; i < cols; i++)
printTableAddHeader(&cont, headers[i], true, 'l');
! /* Check if table is a view */
! if (tableinfo.relkind == 'v' && verbose)
{
PGresult *result;
--- 1432,1439 ----
for (i = 0; i < cols; i++)
printTableAddHeader(&cont, headers[i], true, 'l');
! /* Check if table is a view or materialized view */
! if ((tableinfo.relkind == 'v' || tableinfo.relkind == 'm') && verbose)
{
PGresult *result;
***************
*** 1500,1506 **** describeOneTableDetails(const char *schemaname,
false, false);
/* Statistics target, if the relkind supports this feature */
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
--- 1521,1528 ----
false, false);
/* Statistics target, if the relkind supports this feature */
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! tableinfo.relkind == 'f')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
***************
*** 1508,1513 **** describeOneTableDetails(const char *schemaname,
--- 1530,1536 ----
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
+ tableinfo.relkind == 'm' ||
tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
***************
*** 1604,1647 **** describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- else if (view_def)
- {
- PGresult *result = NULL;
-
- /* Footer information about a view */
- printTableAddFooter(&cont, _("View definition:"));
- printTableAddFooter(&cont, view_def);
-
- /* print rules */
- if (tableinfo.hasrules)
- {
- printfPQExpBuffer(&buf,
- "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
- "FROM pg_catalog.pg_rewrite r\n"
- "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
- oid);
- result = PSQLexec(buf.data, false);
- if (!result)
- goto error_return;
-
- if (PQntuples(result) > 0)
- {
- printTableAddFooter(&cont, _("Rules:"));
- for (i = 0; i < PQntuples(result); i++)
- {
- const char *ruledef;
-
- /* Everything after "CREATE RULE" is echoed verbatim */
- ruledef = PQgetvalue(result, i, 1);
- ruledef += 12;
-
- printfPQExpBuffer(&buf, " %s", ruledef);
- printTableAddFooter(&cont, buf.data);
- }
- }
- PQclear(result);
- }
- }
else if (tableinfo.relkind == 'S')
{
/* Footer information about a sequence */
--- 1627,1632 ----
***************
*** 1680,1686 **** describeOneTableDetails(const char *schemaname,
*/
PQclear(result);
}
! else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
/* Footer information about a table */
PGresult *result = NULL;
--- 1665,1672 ----
*/
PQclear(result);
}
! else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! tableinfo.relkind == 'f')
{
/* Footer information about a table */
PGresult *result = NULL;
***************
*** 1881,1887 **** describeOneTableDetails(const char *schemaname,
}
/* print rules */
! if (tableinfo.hasrules)
{
if (pset.sversion >= 80300)
{
--- 1867,1873 ----
}
/* print rules */
! if (tableinfo.hasrules && tableinfo.relkind != 'm')
{
if (pset.sversion >= 80300)
{
***************
*** 1976,1981 **** describeOneTableDetails(const char *schemaname,
--- 1962,2006 ----
}
}
+ if (view_def)
+ {
+ PGresult *result = NULL;
+
+ /* Footer information about a view */
+ printTableAddFooter(&cont, _("View definition:"));
+ printTableAddFooter(&cont, view_def);
+
+ /* print rules */
+ if (tableinfo.hasrules)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true))\n"
+ "FROM pg_catalog.pg_rewrite r\n"
+ "WHERE r.ev_class = '%s' AND r.rulename != '_RETURN' ORDER BY 1;",
+ oid);
+ result = PSQLexec(buf.data, false);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ printTableAddFooter(&cont, _("Rules:"));
+ for (i = 0; i < PQntuples(result); i++)
+ {
+ const char *ruledef;
+
+ /* Everything after "CREATE RULE" is echoed verbatim */
+ ruledef = PQgetvalue(result, i, 1);
+ ruledef += 12;
+
+ printfPQExpBuffer(&buf, " %s", ruledef);
+ printTableAddFooter(&cont, buf.data);
+ }
+ }
+ PQclear(result);
+ }
+ }
+
/*
* Print triggers next, if any (but only user-defined triggers). This
* could apply to either a table or a view.
***************
*** 2099,2105 **** describeOneTableDetails(const char *schemaname,
/*
* Finish printing the footer information about a table.
*/
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'f')
{
PGresult *result;
int tuples;
--- 2124,2131 ----
/*
* Finish printing the footer information about a table.
*/
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
! tableinfo.relkind == 'f')
{
PGresult *result;
int tuples;
***************
*** 2296,2302 **** add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
! if (relkind == 'r' || relkind == 'i')
{
/*
* We ignore the database default tablespace so that users not using
--- 2322,2328 ----
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
! if (relkind == 'r' || relkind == 'm' || relkind == 'i')
{
/*
* We ignore the database default tablespace so that users not using
***************
*** 2578,2583 **** listDbRoleSettings(const char *pattern, const char *pattern2)
--- 2604,2610 ----
* t - tables
* i - indexes
* v - views
+ * m - materialized views
* s - sequences
* E - foreign table (Note: different from 'f', the relkind value)
* (any order of the above is fine)
***************
*** 2589,2594 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2616,2622 ----
bool showTables = strchr(tabtypes, 't') != NULL;
bool showIndexes = strchr(tabtypes, 'i') != NULL;
bool showViews = strchr(tabtypes, 'v') != NULL;
+ bool showMatViews = strchr(tabtypes, 'm') != NULL;
bool showSeq = strchr(tabtypes, 's') != NULL;
bool showForeign = strchr(tabtypes, 'E') != NULL;
***************
*** 2597,2604 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showSeq || showForeign))
! showTables = showViews = showSeq = showForeign = true;
initPQExpBuffer(&buf);
--- 2625,2632 ----
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
! showTables = showViews = showMatViews = showSeq = showForeign = true;
initPQExpBuffer(&buf);
***************
*** 2609,2620 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' WHEN 's' THEN '%s' WHEN 'f' THEN '%s' END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
gettext_noop("view"),
gettext_noop("index"),
gettext_noop("sequence"),
gettext_noop("special"),
--- 2637,2657 ----
printfPQExpBuffer(&buf,
"SELECT n.nspname as \"%s\",\n"
" c.relname as \"%s\",\n"
! " CASE c.relkind"
! " WHEN 'r' THEN '%s'"
! " WHEN 'v' THEN '%s'"
! " WHEN 'm' THEN '%s'"
! " WHEN 'i' THEN '%s'"
! " WHEN 'S' THEN '%s'"
! " WHEN 's' THEN '%s'"
! " WHEN 'f' THEN '%s'"
! " END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
gettext_noop("Name"),
gettext_noop("table"),
gettext_noop("view"),
+ gettext_noop("materialized view"),
gettext_noop("index"),
gettext_noop("sequence"),
gettext_noop("special"),
***************
*** 2660,2665 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2697,2704 ----
appendPQExpBuffer(&buf, "'r',");
if (showViews)
appendPQExpBuffer(&buf, "'v',");
+ if (showMatViews)
+ appendPQExpBuffer(&buf, "'m',");
if (showIndexes)
appendPQExpBuffer(&buf, "'i',");
if (showSeq)
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
***************
*** 217,222 **** slashUsage(unsigned short int pager)
--- 217,223 ----
fprintf(output, _(" \\di[S+] [PATTERN] list indexes\n"));
fprintf(output, _(" \\dl list large objects, same as \\lo_list\n"));
fprintf(output, _(" \\dL[S+] [PATTERN] list procedural languages\n"));
+ fprintf(output, _(" \\dm[S+] [PATTERN] list materialized views\n"));
fprintf(output, _(" \\dn[S+] [PATTERN] list schemas\n"));
fprintf(output, _(" \\do[S] [PATTERN] list operators\n"));
fprintf(output, _(" \\dO[S+] [PATTERN] list collations\n"));
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 435,445 **** static const SchemaQuery Query_for_list_of_relations = {
NULL
};
! static const SchemaQuery Query_for_list_of_tsvf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'S', 'v', 'f')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
--- 435,445 ----
NULL
};
! static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
***************
*** 450,460 **** static const SchemaQuery Query_for_list_of_tsvf = {
NULL
};
! static const SchemaQuery Query_for_list_of_tf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'f')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
--- 450,475 ----
NULL
};
! static const SchemaQuery Query_for_list_of_tmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
! "c.relkind IN ('r', 'm', 'f')",
! /* viscondition */
! "pg_catalog.pg_table_is_visible(c.oid)",
! /* namespace */
! "c.relnamespace",
! /* result */
! "pg_catalog.quote_ident(c.relname)",
! /* qualresult */
! NULL
! };
!
! static const SchemaQuery Query_for_list_of_tm = {
! /* catname */
! "pg_catalog.pg_class c",
! /* selcondition */
! "c.relkind IN ('r', 'm')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
***************
*** 480,485 **** static const SchemaQuery Query_for_list_of_views = {
--- 495,515 ----
NULL
};
+ static const SchemaQuery Query_for_list_of_matviews = {
+ /* catname */
+ "pg_catalog.pg_class c",
+ /* selcondition */
+ "c.relkind IN ('m')",
+ /* viscondition */
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ /* namespace */
+ "c.relnamespace",
+ /* result */
+ "pg_catalog.quote_ident(c.relname)",
+ /* qualresult */
+ NULL
+ };
+
/*
* Queries to get lists of names of various kinds of things, possibly
***************
*** 743,748 **** static const pgsql_thing_t words_after_create[] = {
--- 773,779 ----
{"GROUP", Query_for_list_of_roles},
{"LANGUAGE", Query_for_list_of_languages},
{"INDEX", NULL, &Query_for_list_of_indexes},
+ {"MATERIALIZED VIEW", NULL, NULL},
{"OPERATOR", NULL, NULL}, /* Querying for this is probably not such a
* good idea. */
{"OWNED", NULL, NULL, THING_NO_CREATE}, /* for DROP OWNED BY ... */
***************
*** 924,930 **** psql_completion(char *text, int start, int end)
static const char *const list_ALTER[] =
{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
"EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
! "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR",
"ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
"USER", "USER MAPPING FOR", "VIEW", NULL};
--- 955,961 ----
static const char *const list_ALTER[] =
{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
"EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
! "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
"ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
"USER", "USER MAPPING FOR", "VIEW", NULL};
***************
*** 1093,1098 **** psql_completion(char *text, int start, int end)
--- 1124,1137 ----
COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
}
+ /* ALTER MATERIALIZED VIEW */
+ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+ pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev_wd, "VIEW") == 0)
+ {
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ }
+
/* ALTER USER,ROLE <name> */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
!(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
***************
*** 1259,1264 **** psql_completion(char *text, int start, int end)
--- 1298,1313 ----
COMPLETE_WITH_LIST(list_ALTERVIEW);
}
+ /* ALTER MATERIALIZED VIEW <name> */
+ else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev2_wd, "VIEW") == 0)
+ {
+ static const char *const list_ALTERMATVIEW[] =
+ {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+ COMPLETE_WITH_LIST(list_ALTERMATVIEW);
+ }
/* ALTER TRIGGER <name>, add ON */
else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
***************
*** 1717,1730 **** psql_completion(char *text, int start, int end)
*/
else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "UNION SELECT 'VERBOSE'");
/*
* If the previous words are CLUSTER VERBOSE produce list of tables
*/
else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* If we have CLUSTER <sth>, then add "USING" */
else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
--- 1766,1779 ----
*/
else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
/*
* If the previous words are CLUSTER VERBOSE produce list of tables
*/
else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
/* If we have CLUSTER <sth>, then add "USING" */
else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
***************
*** 1771,1777 **** psql_completion(char *text, int start, int end)
{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
"SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
! "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
--- 1820,1826 ----
{"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
"FOREIGN DATA WRAPPER", "FOREIGN TABLE",
"SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
! "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
"TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
***************
*** 1945,1951 **** psql_completion(char *text, int start, int end)
pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
--- 1994,2000 ----
pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
***************
*** 2043,2049 **** psql_completion(char *text, int start, int end)
pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
{
static const char *const list_TEMP[] =
! {"SEQUENCE", "TABLE", "VIEW", NULL};
COMPLETE_WITH_LIST(list_TEMP);
}
--- 2092,2098 ----
pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
{
static const char *const list_TEMP[] =
! {"SEQUENCE", "TABLE", "VIEW", "MATERIALIZED VIEW", NULL};
COMPLETE_WITH_LIST(list_TEMP);
}
***************
*** 2051,2057 **** psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
{
! COMPLETE_WITH_CONST("TABLE");
}
/* CREATE TABLESPACE */
--- 2100,2109 ----
else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
{
! static const char *const list_UNLOGGED[] =
! {"TABLE", "MATERIALIZED VIEW", NULL};
!
! COMPLETE_WITH_LIST(list_UNLOGGED);
}
/* CREATE TABLESPACE */
***************
*** 2220,2225 **** psql_completion(char *text, int start, int end)
--- 2272,2290 ----
pg_strcasecmp(prev_wd, "AS") == 0)
COMPLETE_WITH_CONST("SELECT");
+ /* CREATE MATERIALIZED VIEW */
+ /* Complete CREATE MATERIALIZED VIEW <name> with AS */
+ else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+ pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev2_wd, "VIEW") == 0)
+ COMPLETE_WITH_CONST("AS");
+ /* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
+ else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+ pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
+ pg_strcasecmp(prev_wd, "AS") == 0)
+ COMPLETE_WITH_CONST("SELECT");
+
/* DECLARE */
else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
{
***************
*** 2346,2351 **** psql_completion(char *text, int start, int end)
--- 2411,2425 ----
COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
}
+
+ /* DROP MATERIALIZED VIEW */
+ else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+ pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev_wd, "VIEW") == 0)
+ {
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+ }
+
else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
(pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
***************
*** 2521,2527 **** psql_completion(char *text, int start, int end)
else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf,
" UNION SELECT 'DATABASE'"
" UNION SELECT 'DOMAIN'"
" UNION SELECT 'FOREIGN DATA WRAPPER'"
--- 2595,2601 ----
else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
pg_strcasecmp(prev_wd, "ON") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
" UNION SELECT 'DATABASE'"
" UNION SELECT 'DOMAIN'"
" UNION SELECT 'FOREIGN DATA WRAPPER'"
***************
*** 2649,2654 **** psql_completion(char *text, int start, int end)
--- 2723,2736 ----
pg_strcasecmp(prev2_wd, "DEFAULT") != 0)
COMPLETE_WITH_CONST("(");
+ /* LOAD MATERIALIZED VIEW */
+ else if (pg_strcasecmp(prev_wd, "LOAD") == 0)
+ COMPLETE_WITH_CONST("MATERIALIZED VIEW");
+ else if (pg_strcasecmp(prev3_wd, "LOAD") == 0 &&
+ pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+ pg_strcasecmp(prev_wd, "VIEW") == 0)
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+
/* LOCK */
/* Complete LOCK [TABLE] with a list of tables */
else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
***************
*** 2751,2757 **** psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
{
if (pg_strcasecmp(prev_wd, "TABLE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
--- 2833,2839 ----
else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
{
if (pg_strcasecmp(prev_wd, "TABLE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
***************
*** 2783,2791 **** psql_completion(char *text, int start, int end)
pg_strcasecmp(prev_wd, "ON") == 0))
{
static const char *const list_SECURITY_LABEL[] =
! {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN",
! "AGGREGATE", "FUNCTION", "DOMAIN", "LARGE OBJECT",
! NULL};
COMPLETE_WITH_LIST(list_SECURITY_LABEL);
}
--- 2865,2873 ----
pg_strcasecmp(prev_wd, "ON") == 0))
{
static const char *const list_SECURITY_LABEL[] =
! {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
! "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "DOMAIN",
! "LARGE OBJECT", NULL};
COMPLETE_WITH_LIST(list_SECURITY_LABEL);
}
***************
*** 2971,2977 **** psql_completion(char *text, int start, int end)
/* TRUNCATE */
else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* UNLISTEN */
else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
--- 3053,3059 ----
/* TRUNCATE */
else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
/* UNLISTEN */
else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
***************
*** 3032,3038 **** psql_completion(char *text, int start, int end)
* VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
*/
else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'FULL'"
" UNION SELECT 'FREEZE'"
" UNION SELECT 'ANALYZE'"
--- 3114,3120 ----
* VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
*/
else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'FULL'"
" UNION SELECT 'FREEZE'"
" UNION SELECT 'ANALYZE'"
***************
*** 3040,3073 **** psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
(pg_strcasecmp(prev_wd, "FULL") == 0 ||
pg_strcasecmp(prev_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'ANALYZE'"
" UNION SELECT 'VERBOSE'");
else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
(pg_strcasecmp(prev2_wd, "FULL") == 0 ||
pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'VERBOSE'");
else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
(pg_strcasecmp(prev2_wd, "FULL") == 0 ||
pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'ANALYZE'");
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "VERBOSE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'ANALYZE'");
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
" UNION SELECT 'VERBOSE'");
else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
(pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* WITH [RECURSIVE] */
--- 3122,3155 ----
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
(pg_strcasecmp(prev_wd, "FULL") == 0 ||
pg_strcasecmp(prev_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'ANALYZE'"
" UNION SELECT 'VERBOSE'");
else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
(pg_strcasecmp(prev2_wd, "FULL") == 0 ||
pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'VERBOSE'");
else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
(pg_strcasecmp(prev2_wd, "FULL") == 0 ||
pg_strcasecmp(prev2_wd, "FREEZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'ANALYZE'");
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "VERBOSE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'ANALYZE'");
else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
" UNION SELECT 'VERBOSE'");
else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
(pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
/* WITH [RECURSIVE] */
***************
*** 3082,3088 **** psql_completion(char *text, int start, int end)
/* ANALYZE */
/* If the previous word is ANALYZE, produce list of tables */
else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tf, NULL);
/* WHERE */
/* Simple case of the word before the where being the table name */
--- 3164,3170 ----
/* ANALYZE */
/* If the previous word is ANALYZE, produce list of tables */
else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
/* WHERE */
/* Simple case of the word before the where being the table name */
***************
*** 3094,3104 **** psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
pg_strcasecmp(prev3_wd, "COPY") != 0 &&
pg_strcasecmp(prev3_wd, "\\copy") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
/* ... JOIN ... */
else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
/* Backslash commands */
/* TODO: \dc \dd \dl */
--- 3176,3186 ----
else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
pg_strcasecmp(prev3_wd, "COPY") != 0 &&
pg_strcasecmp(prev3_wd, "\\copy") != 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
/* ... JOIN ... */
else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
/* Backslash commands */
/* TODO: \dc \dd \dl */
***************
*** 3138,3144 **** psql_completion(char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
|| strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
--- 3220,3226 ----
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
|| strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
! COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
***************
*** 3150,3155 **** psql_completion(char *text, int start, int end)
--- 3232,3239 ----
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+ else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
/* must be at end of \d list */
else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
*** a/src/include/catalog/catversion.h
--- b/src/include/catalog/catversion.h
***************
*** 53,58 ****
*/
/* yyyymmddN */
! #define CATALOG_VERSION_NO 201210071
#endif
--- 53,58 ----
*/
/* yyyymmddN */
! #define CATALOG_VERSION_NO 201211030
#endif
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
***************
*** 66,71 **** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
--- 66,79 ----
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
+
+ /*
+ * When relisvalid is set to false, a query which references the relation
+ * will throw an error saying the relation is not available. The initial
+ * intended use is to flag whether a materialized view has been populated.
+ * It may prove useful for other purposes.
+ */
+ bool relisvalid; /* is valid for use in queries */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
***************
*** 91,97 **** typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
! #define Natts_pg_class 27
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
--- 99,105 ----
* ----------------
*/
! #define Natts_pg_class 28
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
***************
*** 116,124 **** typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 22
#define Anum_pg_class_relhastriggers 23
#define Anum_pg_class_relhassubclass 24
! #define Anum_pg_class_relfrozenxid 25
! #define Anum_pg_class_relacl 26
! #define Anum_pg_class_reloptions 27
/* ----------------
* initial contents of pg_class
--- 124,133 ----
#define Anum_pg_class_relhasrules 22
#define Anum_pg_class_relhastriggers 23
#define Anum_pg_class_relhassubclass 24
! #define Anum_pg_class_relisvalid 25
! #define Anum_pg_class_relfrozenxid 26
! #define Anum_pg_class_relacl 27
! #define Anum_pg_class_reloptions 28
/* ----------------
* initial contents of pg_class
***************
*** 130,142 **** typedef FormData_pg_class *Form_pg_class;
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DESCR("");
--- 139,151 ----
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
! DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f t 3 _null_ _null_ ));
DESCR("");
***************
*** 147,152 **** DESCR("");
--- 156,162 ----
#define RELKIND_VIEW 'v' /* view */
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
+ #define RELKIND_MATVIEW 'm' /* materialized view */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
*** a/src/include/commands/createas.h
--- b/src/include/commands/createas.h
***************
*** 19,24 ****
--- 19,28 ----
#include "tcop/dest.h"
+ extern Query *SetupForCreateTableAs(Query *query, IntoClause *into,
+ const char *queryString,
+ ParamListInfo params, DestReceiver *dest);
+
extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
ParamListInfo params, char *completionTag);
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
***************
*** 63,74 **** extern void ExplainInitState(ExplainState *es);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
! ExplainState *es,
! const char *queryString, ParamListInfo params);
extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
! ExplainState *es,
! const char *queryString, ParamListInfo params);
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
--- 63,74 ----
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
! ExplainState *es, const char *queryString,
! DestReceiver *dest, ParamListInfo params);
extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
! ExplainState *es, const char *queryString,
! DestReceiver *dest, ParamListInfo params);
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
*** /dev/null
--- b/src/include/commands/matview.h
***************
*** 0 ****
--- 1,25 ----
+ /*-------------------------------------------------------------------------
+ *
+ * matview.h
+ * prototypes for matview.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/matview.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef MATVIEW_H
+ #define MATVIEW_H
+
+ #include "nodes/params.h"
+ #include "tcop/dest.h"
+
+ extern void ExecLoadMatView(LoadMatViewStmt *stmt, const char *queryString,
+ ParamListInfo params, char *completionTag);
+
+ extern DestReceiver *CreateTransientRelDestReceiver(Oid oid);
+
+ #endif /* MATVIEW_H */
*** a/src/include/commands/tablecmds.h
--- b/src/include/commands/tablecmds.h
***************
*** 50,55 **** extern void CheckTableNotInUse(Relation rel, const char *stmt);
--- 50,56 ----
extern void ExecuteTruncate(TruncateStmt *stmt);
extern void SetRelationHasSubclass(Oid relationId, bool relhassubclass);
+ extern void SetRelationIsValid(Oid relationId, bool relisvalid);
extern void renameatt(RenameStmt *stmt);
***************
*** 78,81 **** extern void AtEOSubXact_on_commit_actions(bool isCommit,
--- 79,84 ----
extern void RangeVarCallbackOwnsTable(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
+ extern bool isQueryUsingTempRelation(Query *query);
+
#endif /* TABLECMDS_H */
*** a/src/include/commands/view.h
--- b/src/include/commands/view.h
***************
*** 18,21 ****
--- 18,23 ----
extern void DefineView(ViewStmt *stmt, const char *queryString);
+ extern void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace);
+
#endif /* VIEW_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 361,366 **** typedef enum NodeTag
--- 361,367 ----
T_AlterExtensionContentsStmt,
T_CreateEventTrigStmt,
T_AlterEventTrigStmt,
+ T_LoadMatViewStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1125,1130 **** typedef enum ObjectType
--- 1125,1131 ----
OBJECT_INDEX,
OBJECT_LANGUAGE,
OBJECT_LARGEOBJECT,
+ OBJECT_MATVIEW,
OBJECT_OPCLASS,
OBJECT_OPERATOR,
OBJECT_OPFAMILY,
***************
*** 2436,2441 **** typedef struct ExplainStmt
--- 2437,2444 ----
* A query written as CREATE TABLE AS will produce this node type natively.
* A query written as SELECT ... INTO will be transformed to this form during
* parse analysis.
+ * A query written as CREATE MATERIALIZED view will produce this node type,
+ * during parse analysis, since it needs all the same data.
*
* The "query" field is handled similarly to EXPLAIN, though note that it
* can be a SELECT or an EXECUTE, but not other DML statements.
***************
*** 2446,2455 **** typedef struct CreateTableAsStmt
--- 2449,2469 ----
NodeTag type;
Node *query; /* the query (see comments above) */
IntoClause *into; /* destination table */
+ ObjectType relkind; /* type of object */
bool is_select_into; /* it was written as SELECT INTO */
} CreateTableAsStmt;
/* ----------------------
+ * LOAD MATERIALIZED VIEW Statement
+ * ----------------------
+ */
+ typedef struct LoadMatViewStmt
+ {
+ NodeTag type;
+ RangeVar *relation; /* relation to insert into */
+ } LoadMatViewStmt;
+
+ /* ----------------------
* Checkpoint Statement
* ----------------------
*/
***************
*** 2506,2512 **** typedef struct ConstraintsSetStmt
typedef struct ReindexStmt
{
NodeTag type;
! ObjectType kind; /* OBJECT_INDEX, OBJECT_TABLE, OBJECT_DATABASE */
RangeVar *relation; /* Table or index to reindex */
const char *name; /* name of database to reindex */
bool do_system; /* include system tables in database case */
--- 2520,2526 ----
typedef struct ReindexStmt
{
NodeTag type;
! ObjectType kind; /* OBJECT_INDEX, OBJECT_TABLE, etc. */
RangeVar *relation; /* Table or index to reindex */
const char *name; /* name of database to reindex */
bool do_system; /* include system tables in database case */
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 80,86 **** typedef struct RangeVar
} RangeVar;
/*
! * IntoClause - target information for SELECT INTO and CREATE TABLE AS
*/
typedef struct IntoClause
{
--- 80,87 ----
} RangeVar;
/*
! * IntoClause - target information for SELECT INTO, CREATE TABLE AS, and
! * CREATE MATERIALIZED VIEW
*/
typedef struct IntoClause
{
***************
*** 92,97 **** typedef struct IntoClause
--- 93,99 ----
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
bool skipData; /* true for WITH NO DATA */
+ char relkind; /* RELKIND_RELATION or RELKIND_MATVIEW */
} IntoClause;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 232,237 **** PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD)
--- 232,238 ----
PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+ PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
*** a/src/include/tcop/dest.h
--- b/src/include/tcop/dest.h
***************
*** 93,99 **** typedef enum
DestTuplestore, /* results sent to Tuplestore */
DestIntoRel, /* results sent to relation (SELECT INTO) */
DestCopyOut, /* results sent to COPY TO code */
! DestSQLFunction /* results sent to SQL-language func mgr */
} CommandDest;
/* ----------------
--- 93,100 ----
DestTuplestore, /* results sent to Tuplestore */
DestIntoRel, /* results sent to relation (SELECT INTO) */
DestCopyOut, /* results sent to COPY TO code */
! DestSQLFunction, /* results sent to SQL-language func mgr */
! DestTransientRel /* results sent to transient relation */
} CommandDest;
/* ----------------
*** a/src/include/tcop/utility.h
--- b/src/include/tcop/utility.h
***************
*** 52,55 **** extern bool CommandIsReadOnly(Node *parsetree);
--- 52,57 ----
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
+ extern bool RelationIsFlaggedAsValid(Oid relid);
+
#endif /* UTILITY_H */
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 1760,1770 **** plpgsql_parse_cwordtype(List *idents)
classStruct = (Form_pg_class) GETSTRUCT(classtup);
/*
! * It must be a relation, sequence, view, composite type, or foreign table
*/
if (classStruct->relkind != RELKIND_RELATION &&
classStruct->relkind != RELKIND_SEQUENCE &&
classStruct->relkind != RELKIND_VIEW &&
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
classStruct->relkind != RELKIND_FOREIGN_TABLE)
goto done;
--- 1760,1772 ----
classStruct = (Form_pg_class) GETSTRUCT(classtup);
/*
! * It must be a relation, sequence, view, materialized view, composite
! * type, or foreign table
*/
if (classStruct->relkind != RELKIND_RELATION &&
classStruct->relkind != RELKIND_SEQUENCE &&
classStruct->relkind != RELKIND_VIEW &&
+ classStruct->relkind != RELKIND_MATVIEW &&
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
classStruct->relkind != RELKIND_FOREIGN_TABLE)
goto done;
***************
*** 1982,1991 **** build_row_from_class(Oid classOid)
classStruct = RelationGetForm(rel);
relname = RelationGetRelationName(rel);
! /* accept relation, sequence, view, composite type, or foreign table */
if (classStruct->relkind != RELKIND_RELATION &&
classStruct->relkind != RELKIND_SEQUENCE &&
classStruct->relkind != RELKIND_VIEW &&
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
classStruct->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
--- 1984,1997 ----
classStruct = RelationGetForm(rel);
relname = RelationGetRelationName(rel);
! /*
! * Accept relation, sequence, view, materialized view, composite type, or
! * foreign table.
! */
if (classStruct->relkind != RELKIND_RELATION &&
classStruct->relkind != RELKIND_SEQUENCE &&
classStruct->relkind != RELKIND_VIEW &&
+ classStruct->relkind != RELKIND_MATVIEW &&
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
classStruct->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
*** a/src/pl/tcl/pltcl.c
--- b/src/pl/tcl/pltcl.c
***************
*** 501,506 **** pltcl_init_load_unknown(Tcl_Interp *interp)
--- 501,507 ----
return;
/* must be table or view, else ignore */
if (!(pmrel->rd_rel->relkind == RELKIND_RELATION ||
+ pmrel->rd_rel->relkind == RELKIND_MATVIEW ||
pmrel->rd_rel->relkind == RELKIND_VIEW))
{
relation_close(pmrel, AccessShareLock);
Kevin Grittner wrote:
Interesting stuff.
/* + * SetRelationIsValid + * Set the value of the relation's relisvalid field in pg_class. + * + * NOTE: caller must be holding an appropriate lock on the relation. + * ShareUpdateExclusiveLock is sufficient. + * + * NOTE: an important side-effect of this operation is that an SI invalidation + * message is sent out to all backends --- including me --- causing plans + * referencing the relation to be rebuilt with the new list of children. + * This must happen even if we find that no change is needed in the pg_class + * row. + */ + void + SetRelationIsValid(Oid relationId, bool relisvalid) + {
It's not clear to me that it's right to do this by doing regular heap
updates here instead of heap_inplace_update. Also, I think this might
end up causing a lot of pg_class tuple churn (at least for matviews that
delete rows at xact end), which would be nice to avoid.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
On 11/14/12 6:28 PM, Kevin Grittner wrote:
- Documentation is incomplete.
...
- There are no regression tests yet.
Do you have any simple test cases you've been using you could attach?
With epic new features like this, when things don't work it's hard to
distinguish between "that just isn't implemented yet" and "the author
never tested that". Having some known good samples you have tested,
even if they're not proper regression tests, would be helpful for
establishing the code baseline works.
--
Greg Smith 2ndQuadrant US greg@2ndQuadrant.com Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com
Kevin,
Attached is a patch that is still WIP but that I think is getting
pretty close to completion. It is not intended to be the be-all and
end-all for materialized views, but the minimum useful feature set --
which is all that I've had time to do for this release. In
particular, the view is only updated on demand by a complete rebuild.
For the next release, I hope to build on this base to allow more
eager and incremental updates, and perhaps a concurrent batch update.
Nice to see this come in!
1. CREATE MATERIALIZED VIEW syntax is stolen directly from CREATE
TABLE AS, with all the same clauses supported. That includes
declaring a materialized view to be temporary or unlogged.
What use would a temporary matview be?
Unlogged is good.
2. MVs don't support inheritance.
In which direction? Can't inherit, or can't be inherited from?
3. MVs can't define foreign keys.
4. MVs can't be the target of foreign keys.
5. MVs can't have triggers.
Makes sense.
9. MVs can't directly be used in a COPY statement, but can be the
source of data using a SELECT.
Hmmm? I don't understand the reason for this.
13. pg_class now has a relisvalid column, which is true if an MV is
truncated or created WITH NO DATA. You can not scan a relation
flagged as invalid.
What error would a user see?
14. ALTER MATERIALIZED VIEW is supported for the options that seemed
to make sense. For example, you can change the tablespace or
schema, but you cannot add or drop column with ALTER.
How would you change the definition of an MV then?
16. To get new data into the MV, the command is LOAD MATERIALIZED
VIEW mat view_name. This seemed more descriptive to me that the
alternatives and avoids declaring any new keywords beyond
MATERIALIZED. If the MV is flagged as relisvalid == false, this
will change it to true.
UPDATE MATERIALIZED VIEW was problematic?
Does LOAD automatically TRUNCATE the view before reloading it? If not,
why not?
It would be good to have some discussion to try to reach a consensus
about whether we need to differentiate between *missing* datat (where
a materialized view which has been loaded WITH NO DATA or TRUNCATEd
and has not been subsequently LOADed) and potentially *stale* data.
If we don't care to distinguish between a view which generated no
rows when it ran and a one for which the query has not been run, we
can avoid adding the relisvalid flag, and we could support UNLOGGED
MVs. Perhaps someone can come up with a better solution to that
problem.
Hmmm. I understand the distinction you're making here, but I'm not sure
it actually matters to the user. MVs, by their nature, always have
potentially stale data. Being empty (in an inaccurate way) is just one
kind of stale data.
It would be nice for the user to have some way to know that a matview is
empty due to never being LOADed or recently being TRUNCATEd. However, I
don't think that relisvalid flag -- and preventing scanning the relation
-- is a good solution. What I'd rather have instead is a timestamp of
when the MV was last LOADed. If the MV was never loaded (or was
truncated) that timestamp would be NULL. Such a timestamp would allow
users to construct all kinds of ad-hoc refresh schemes for MVs which
would not be possible without it.
I don't see how this relates to UNLOGGED matviews either way.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
On Wed, 2012-11-14 at 21:28 -0500, Kevin Grittner wrote:
Attached is a patch that is still WIP but that I think is getting
pretty close to completion. It is not intended to be the be-all and
end-all for materialized views, but the minimum useful feature set --
which is all that I've had time to do for this release. In
particular, the view is only updated on demand by a complete rebuild.
For the next release, I hope to build on this base to allow more
eager and incremental updates, and perhaps a concurrent batch update.
The documentation says that a materialized view is basically a
create-table-as-select except that it remembers the query. Would you say
that there is a compelling use case for this alone, or is this a
building block for more sophisticated materialized view support (e.g.
eager updating) later?
Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
The documentation says that a materialized view is basically a
create-table-as-select except that it remembers the query. Would you say
that there is a compelling use case for this alone, or is this a
building block for more sophisticated materialized view support (e.g.
eager updating) later?
The implementation of the re-LOAD'ing command makes it already
worthwile. Bonus point if locking is limited to when the new content is
all computer and ready, but even without that, I want to have it. ;)
I'd bikeshed and prefer the UPDATE MATERIALIZED VIEW nsp.foo; of course.
The alternative is creating a view, a matching table and a stored
procedure that will implement the rebuilding, for each mat view you want
to have. So that's already a big step forward in my eyes.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Alvaro Herrera wrote:
It's not clear to me that it's right to do this by doing regular heap
updates here instead of heap_inplace_update. Also, I think this might
end up causing a lot of pg_class tuple churn (at least for matviews that
delete rows at xact end), which would be nice to avoid.
If we keep the flag, I will look into heap_inplace_update.
Thanks!
-Kevin
Import Notes
Resolved by subject fallback
Greg Smith wrote:
On 11/14/12 6:28 PM, Kevin Grittner wrote:
- Documentation is incomplete.
...
- There are no regression tests yet.Do you have any simple test cases you've been using you could attach?
With epic new features like this, when things don't work it's hard to
distinguish between "that just isn't implemented yet" and "the author
never tested that". Having some known good samples you have tested,
even if they're not proper regression tests, would be helpful for
establishing the code baseline works.
I can probably post something along those lines Monday. Sorry for the
delay. Basically, though, I tried to state everything that I know of
which is not yet done and working; so if you find something which
doesn't behave as you would expect, please let me know. It's well
within the realm of possibility that there are issues that I didn't
think of. I certainly can fall into the tendency of programmers to
think about testing those things which I thought to cover in the code.
-Kevin
Import Notes
Resolved by subject fallback
On Fri, Nov 16, 2012 at 7:13 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Jeff Davis <pgsql@j-davis.com> writes:
The documentation says that a materialized view is basically a
create-table-as-select except that it remembers the query. Would you say
that there is a compelling use case for this alone, or is this a
building block for more sophisticated materialized view support (e.g.
eager updating) later?The implementation of the re-LOAD'ing command makes it already
worthwile. Bonus point if locking is limited to when the new content is
all computer and ready, but even without that, I want to have it. ;)
Seconded. Background lock free refresh of materialization table would
be wonderful. But moving dependency between source and materialized
table out of plpgsql function and into defined schema justifies
feature on its own merits.
merlin
Josh Berkus wrote:
1. CREATE MATERIALIZED VIEW syntax is stolen directly from CREATE
TABLE AS, with all the same clauses supported. That includes
declaring a materialized view to be temporary or unlogged.What use would a temporary matview be?
It would be essentially like a temporary table, with all the same
persistence options. I'm not really sure how often it will be more
useful than a temporary table before we have incremental maintenance
of materialized views; once we have that, though, it seems likely
that there could be reasonable use cases.
Unlogged is good.
I agree that there are likely to be more use cases for this than
temp MVs. Unfortunately, I've had a hard time figuring out how to
flag an MV which is empty because its contents were lost after a
crash with preventing people from using an MV which hasn't been
populated, which has the potential to silently return incorrect
results.
2. MVs don't support inheritance.
In which direction? Can't inherit, or can't be inherited from?
The table inheritance has not been implemented in either direction
for MVs. It didn't seem clear to me that there were reasonable use
cases. Do you see any?
9. MVs can't directly be used in a COPY statement, but can be the
source of data using a SELECT.Hmmm? I don't understand the reason for this.
Consistency. There are other object types which seem to enforce this
rule for no reason that I can see beyond maybe a desire to have both
directions of COPY work with the same set of objects. If I remember
correctly, allowing this would eliminate one line of code from the
patch, so if sentiment is in favor of it, it is very easily done.
13. pg_class now has a relisvalid column, which is true if an MV is
truncated or created WITH NO DATA. You can not scan a relation
flagged as invalid.What error would a user see?
I can more directly answer that on Monday. If you enable the body of
the function which makes the relisvalid check you can see the messages.
I commented it out because I have not yet figured out how to suppress
the check for a LOAD MV command.
14. ALTER MATERIALIZED VIEW is supported for the options that seemed
to make sense. For example, you can change the tablespace or
schema, but you cannot add or drop column with ALTER.How would you change the definition of an MV then?
At this point you would need to drop and re-create the MV. If we
want to add columns to an MV or change what an existing column holds,
perhaps we could have an ALTER MV which changed the SELECT statement
that populates the MV? I would prefer to leave that to a later patch,
though -- it seems like a bit of a minefield compared to what is
being implemented in this patch.
16. To get new data into the MV, the command is LOAD MATERIALIZED
VIEW mat view_name. This seemed more descriptive to me that the
alternatives and avoids declaring any new keywords beyond
MATERIALIZED. If the MV is flagged as relisvalid == false, this
will change it to true.UPDATE MATERIALIZED VIEW was problematic?
Not technically, really, but I saw two reasons that I preferred LOAD MV:
1. It seems to me to better convey that the entire contents of the MV
will be built from scratch, rather than incrementally adjusted.
2. We haven't hashed out the syntax for more aggressive maintenance of
an MV, and it seemed like UPDATE MV might be syntax we would want to
use for something which updated selected parts of an MV when we do.
Does LOAD automatically TRUNCATE the view before reloading it? If not,
why not?
It builds a new heap and moves it into place. When the transaction
running LMV commits, the old heap is deleted. In implementation it is
closer to CLUSTER or the new VACUUM FULL than TRUNCATE followed by
creating a new table. This allows all permissions, etc., to stay in
place.
It would be good to have some discussion to try to reach a consensus
about whether we need to differentiate between *missing* datat (where
a materialized view which has been loaded WITH NO DATA or TRUNCATEd
and has not been subsequently LOADed) and potentially *stale* data.
If we don't care to distinguish between a view which generated no
rows when it ran and a one for which the query has not been run, we
can avoid adding the relisvalid flag, and we could support UNLOGGED
MVs. Perhaps someone can come up with a better solution to that
problem.Hmmm. I understand the distinction you're making here, but I'm not sure
it actually matters to the user. MVs, by their nature, always have
potentially stale data. Being empty (in an inaccurate way) is just one
kind of stale data.
Robert feels the same way, but I disagree. Some MVs will not be terribly
volatile. In my view there is a big difference between having a "top ten"
list which might be based on yesterday's base tables rather than the base
table states as of this moment, and having a "top ten" list with no
entries. If you want to, for example, take some action if an order comes
in for one of your top customers, and a different path for other
customers, suddenly treating all of your long-time top customers as not
being so, without any squawk from the database, seems dangerous.
It would be nice for the user to have some way to know that a matview is
empty due to never being LOADed or recently being TRUNCATEd. However, I
don't think that relisvalid flag -- and preventing scanning the relation
-- is a good solution. What I'd rather have instead is a timestamp of
when the MV was last LOADed. If the MV was never loaded (or was
truncated) that timestamp would be NULL. Such a timestamp would allow
users to construct all kinds of ad-hoc refresh schemes for MVs which
would not be possible without it.
I see your point there; I'll think about that. My take was more that MVs
would often be refreshed by crontab, and that you would want to keep
subsequent steps from running and generating potentially plausible but
completely inaccurate results if the LMV failed.
I don't see how this relates to UNLOGGED matviews either way.
UNLOGGED tables and indexes are made empty on crash recovery by copying
the initialization fork over the "normal" relations. Care was taken to
avoid needing to connect to each database in turn to complete that
recovery. This style of recovery can't really set the relisvalid flag, as
far as I can see; which leaves us choosing between unlogged MVs and
knowing whether they hold valid data -- unless someone has a better idea.
-Kevin
Import Notes
Resolved by subject fallback
Jeff Davis wrote:
On Wed, 2012-11-14 at 21:28 -0500, Kevin Grittner wrote:
Attached is a patch that is still WIP but that I think is getting
pretty close to completion. It is not intended to be the be-all and
end-all for materialized views, but the minimum useful feature set --
which is all that I've had time to do for this release. In
particular, the view is only updated on demand by a complete rebuild.
For the next release, I hope to build on this base to allow more
eager and incremental updates, and perhaps a concurrent batch update.The documentation says that a materialized view is basically a
create-table-as-select except that it remembers the query. Would you say
that there is a compelling use case for this alone, or is this a
building block for more sophisticated materialized view support (e.g.
eager updating) later?
IMV, this has some slight value as it stands, although perhaps not enough
to justify a patch this big. The idea is that with this much in place,
patches to implement more aggressive and incremental maintenance of the
MV data become possible. So I think the bar it should pass for commit is
that it seems a sane basis for that, while providing some functionality
which people will find useful.
-Kevin
Import Notes
Resolved by subject fallback
On Thu, Nov 15, 2012 at 1:36 PM, Josh Berkus <josh@agliodbs.com> wrote:
Hmmm. I understand the distinction you're making here, but I'm not sure
it actually matters to the user. MVs, by their nature, always have
potentially stale data. Being empty (in an inaccurate way) is just one
kind of stale data.
This is my feeling also.
I don't see how this relates to UNLOGGED matviews either way.
Right now, Kevin has things set up so that when you do "TRUNCATE mv",
it clears the relisvalid flag. If we allowed unlogged materialized
views, the table would be automatically truncated on a crash, but
there wouldn't be any way to clear relisvalid in that situation, so
Kevin felt we should simply disable unlogged MVs. Personally, I'm not
excited about having a relisvalid flag at all, and doubly not excited
if it means we can't have unlogged MVs.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 16 November 2012 16:25, Kevin Grittner <kgrittn@mail.com> wrote:
Josh Berkus wrote:
Unlogged is good.
I agree that there are likely to be more use cases for this than
temp MVs. Unfortunately, I've had a hard time figuring out how to
flag an MV which is empty because its contents were lost after a
crash with preventing people from using an MV which hasn't been
populated, which has the potential to silently return incorrect
results.
pg_class.relisvalid = false.. getting rid of the use for truncated MVs?
--
Thom
"Kevin Grittner" <kgrittn@mail.com> writes:
Josh Berkus wrote:
What use would a temporary matview be?
It would be essentially like a temporary table, with all the same
persistence options. I'm not really sure how often it will be more
useful than a temporary table before we have incremental maintenance
of materialized views; once we have that, though, it seems likely
that there could be reasonable use cases.
One of the principal attributes of a temp table is that its contents
aren't (reliably) accessible from anywhere except the owning backend.
Not sure where you're going to hide the incremental maintenance in
that scenario.
The table inheritance has not been implemented in either direction
for MVs. It didn't seem clear to me that there were reasonable use
cases. Do you see any?
We don't have inheritance for views, so how would we have it for
materialized views?
regards, tom lane
On 15 November 2012 02:28, Kevin Grittner <kgrittn@mail.com> wrote:
Attached is a patch that...
Got this error:
postgres=# create view v_test as select 1;
CREATE VIEW
postgres=# create materialized view mv_test as select * from v_test;
ERROR: could not open file "base/12064/16425": No such file or directory
--
Thom
By chance (?) I got similar one today too, when dropping extension:
ERROR: could not open file "base/12623/12548": No such file or directory
I thought something might have gone wrong during Linux upgrade 2 days
ago, but it's not likely that we both have the issue.
I wonder if something is broken in the catalog. The last commit I have
in my environment is
commit 4af3dda13601d859a20425e3554533fde0549056
Author: Peter Eisentraut <peter_e@gmx.net>
Date: Sun Oct 28 10:35:46 2012 -0400
Kind regards,
Tony.
Show quoted text
On 11/16/2012 06:14 PM, Thom Brown wrote:
On 15 November 2012 02:28, Kevin Grittner <kgrittn@mail.com
<mailto:kgrittn@mail.com>> wrote:Attached is a patch that...
Got this error:
postgres=# create view v_test as select 1;
CREATE VIEW
postgres=# create materialized view mv_test as select * from v_test;
ERROR: could not open file "base/12064/16425": No such file or directory--
Thom
Kevin,
I agree that there are likely to be more use cases for this than
temp MVs. Unfortunately, I've had a hard time figuring out how to
flag an MV which is empty because its contents were lost after a
crash with preventing people from using an MV which hasn't been
populated, which has the potential to silently return incorrect
results.
See below.
2. MVs don't support inheritance.
In which direction? Can't inherit, or can't be inherited from?
The table inheritance has not been implemented in either direction
for MVs. It didn't seem clear to me that there were reasonable use
cases. Do you see any?
No, I just wanted clarity on this. I can see a strong case for
eventually supporting CREATE MATERIALIZED VIEW matview_1 LIKE matview,
in order to "copy" mativews, though.
Consistency. There are other object types which seem to enforce this
rule for no reason that I can see beyond maybe a desire to have both
directions of COPY work with the same set of objects. If I remember
correctly, allowing this would eliminate one line of code from the
patch, so if sentiment is in favor of it, it is very easily done.
There's going to be a pretty strong demand for COPY FROM matviews.
Forcing the user to use COPY FROM ( SELECT ... ) will be seen as
arbitrary and unintuitive.
How would you change the definition of an MV then?
At this point you would need to drop and re-create the MV. If we
want to add columns to an MV or change what an existing column holds,
perhaps we could have an ALTER MV which changed the SELECT statement
that populates the MV? I would prefer to leave that to a later patch,
though -- it seems like a bit of a minefield compared to what is
being implemented in this patch.
I agree that it should be a later patch.
Not technically, really, but I saw two reasons that I preferred LOAD MV:
1. It seems to me to better convey that the entire contents of the MV
will be built from scratch, rather than incrementally adjusted.
2. We haven't hashed out the syntax for more aggressive maintenance of
an MV, and it seemed like UPDATE MV might be syntax we would want to
use for something which updated selected parts of an MV when we do.
Hmmm, I see your point. So "LOAD" would recreate, and (when supported)
UPDATE would incrementally update?
It builds a new heap and moves it into place. When the transaction
running LMV commits, the old heap is deleted. In implementation it is
closer to CLUSTER or the new VACUUM FULL than TRUNCATE followed by
creating a new table. This allows all permissions, etc., to stay in
place.
OK, so same effect as a truncate.
Robert feels the same way, but I disagree. Some MVs will not be terribly
volatile. In my view there is a big difference between having a "top ten"
list which might be based on yesterday's base tables rather than the base
table states as of this moment, and having a "top ten" list with no
entries. If you want to, for example, take some action if an order comes
in for one of your top customers, and a different path for other
customers, suddenly treating all of your long-time top customers as not
being so, without any squawk from the database, seems dangerous.
Right, but a relisvalid flag just tells me that the matview was updated
at sometime in the past, and not *when* it was updated. It could have
been 3 years ago. The fact that it was updated at some indefinite time
is fairly valueless information.
There's a rule in data warehousing which says that it's better to have
no data (and know that you have no data) than to have incorrect data.
I see your point there; I'll think about that. My take was more that MVs
would often be refreshed by crontab, and that you would want to keep
subsequent steps from running and generating potentially plausible but
completely inaccurate results if the LMV failed.
Yeah, that too. Also, a timestamp it would make it easy to double-check
if the cron job was failing or had been disabled.
UNLOGGED tables and indexes are made empty on crash recovery by copying
the initialization fork over the "normal" relations. Care was taken to
avoid needing to connect to each database in turn to complete that
recovery. This style of recovery can't really set the relisvalid flag, as
far as I can see; which leaves us choosing between unlogged MVs and
knowing whether they hold valid data -- unless someone has a better idea.
Yeah, well, whether we have relisvalid or mvlastupdate, we're going to
have to work out some way to have that field react to changes to the
table overall. I don't know *how*, but it's something we'll have to solve.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
"Kevin Grittner" <kgrittn@mail.com> writes:
UPDATE MATERIALIZED VIEW was problematic?
Not technically, really, but I saw two reasons that I preferred LOAD MV:
1. It seems to me to better convey that the entire contents of the MV
will be built from scratch, rather than incrementally adjusted.
2. We haven't hashed out the syntax for more aggressive maintenance of
an MV, and it seemed like UPDATE MV might be syntax we would want to
use for something which updated selected parts of an MV when we do.
Good point, and while I'm in the mood for some grammar input, here's a
try:
ALTER MATERIALIZED VIEW foo RESET;
ALTER MATERIALIZED VIEW foo UPDATE;
I think such wholesale operations make more sense as ALTER statement
than as UPDATE statements.
It builds a new heap and moves it into place. When the transaction
running LMV commits, the old heap is deleted. In implementation it is
closer to CLUSTER or the new VACUUM FULL than TRUNCATE followed by
creating a new table. This allows all permissions, etc., to stay in
place.
When you say closer to CLUSTER, do you include the Access Exclusive Lock
that forbids reading the previous version's data while you prepare the
new one? That would be very bad and I wouldn't understand the need to,
in the scope of MATERIALIZED VIEWs which are by definition lagging
behind…
If as I think you don't have that limitation in your implementation,
it's awesome and just what I was hoping to read :)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Josh Berkus <josh@agliodbs.com> writes:
There's going to be a pretty strong demand for COPY FROM matviews.
Forcing the user to use COPY FROM ( SELECT ... ) will be seen as
arbitrary and unintuitive.
You could make that same claim about plain views, but in point of
fact the demand for making them work in COPY has been minimal.
So I'm not convinced this is an essential first-cut feature.
We can always add it later.
regards, tom lane
Thom Brown wrote:
postgres=# create view v_test as select 1;
CREATE VIEW
postgres=# create materialized view mv_test as select * from v_test;
ERROR: could not open file "base/12064/16425": No such file or directory
Thanks for the report; will investigate.
-Kevin
Import Notes
Resolved by subject fallback
Robert Haas wrote:
Josh Berkus wrote:
Being empty (in an inaccurate way) is just one kind of stale data.
This is my feeling also.
If you had an MV summarizing Wisconsin courts cumulative case counts
by case type, "empty" would not have been a valid "stale" state for
over 150 years. That is a degree of staleness that IMV is not just a
quantitative degree of staleness, as if a nightly recalculation had
failed to occur, but a qualitatively different state entirely. While
you may or may not want to use the stale data if last night's regen
failed, and so it should be under application control, I can't
imagine a situation where you would want to proceed if the MV didn't
have data that had at some time been correct -- preferrably at some
time since the invention of digital electronic computers. Could you
provide an example where it would be a good thing to do so?
-Kevin
Import Notes
Resolved by subject fallback
You could make that same claim about plain views, but in point of
fact the demand for making them work in COPY has been minimal.
So I'm not convinced this is an essential first-cut feature.
We can always add it later.
Of course. I just had the impression that we could support COPY FROM by
*deleting* a couple lines from Kevin's patch, rather than it being extra
work.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
Josh Berkus <josh@agliodbs.com> writes:
You could make that same claim about plain views, but in point of
fact the demand for making them work in COPY has been minimal.
So I'm not convinced this is an essential first-cut feature.
We can always add it later.
Of course. I just had the impression that we could support COPY FROM by
*deleting* a couple lines from Kevin's patch, rather than it being extra
work.
Even if it happens to be trivial in the current patch, it's an added
functional requirement that we might later regret having cavalierly
signed up for. And, as noted upthread, relations that support only
one direction of COPY don't exist at the moment; that would be adding
an asymmetry that we might later regret, too.
regards, tom lane
On 16 November 2012 11:25, Kevin Grittner <kgrittn@mail.com> wrote:
16. To get new data into the MV, the command is LOAD MATERIALIZED
VIEW mat view_name. This seemed more descriptive to me that the
alternatives and avoids declaring any new keywords beyond
MATERIALIZED. If the MV is flagged as relisvalid == false, this
will change it to true.UPDATE MATERIALIZED VIEW was problematic?
Not technically, really, but I saw two reasons that I preferred LOAD MV:
1. It seems to me to better convey that the entire contents of the MV
will be built from scratch, rather than incrementally adjusted.
2. We haven't hashed out the syntax for more aggressive maintenance of
an MV, and it seemed like UPDATE MV might be syntax we would want to
use for something which updated selected parts of an MV when we do.Does LOAD automatically TRUNCATE the view before reloading it? If not,
why not?It builds a new heap and moves it into place. When the transaction
running LMV commits, the old heap is deleted. In implementation it is
closer to CLUSTER or the new VACUUM FULL than TRUNCATE followed by
creating a new table. This allows all permissions, etc., to stay in
place.
This seems very similar to the REPLACE command we discussed earlier,
except this is restricted to Mat Views.
If we're going to have this, I would prefer a whole command.
e.g. REPLACE matviewname REFRESH
that would also allow
REPLACE tablename AS query
Same thing under the covers, just more widely applicable and thus more useful.
Either way, I don't much like overloading the use of LOAD, which
already has a very different meaning.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Josh Berkus wrote:
It would be nice for the user to have some way to know that a matview is
empty due to never being LOADed or recently being TRUNCATEd. However, I
don't think that relisvalid flag -- and preventing scanning the relation
-- is a good solution. What I'd rather have instead is a timestamp of
when the MV was last LOADed. If the MV was never loaded (or was
truncated) that timestamp would be NULL. Such a timestamp would allow
users to construct all kinds of ad-hoc refresh schemes for MVs which
would not be possible without it.
+1
Kevin Grittner wrote:
I see your point there; I'll think about that. My take was more that MVs
would often be refreshed by crontab, and that you would want to keep
subsequent steps from running and generating potentially plausible but
completely inaccurate results if the LMV failed.
If one of these subsequent steps doesn't care if refresh
failed once, it shouldn't be forced to fail. I imagine
that for many applications yesterday's data can be good enough.
Those that care should check the timestamp.
Yours,
Laurenz Albe
Albe Laurenz wrote:
Kevin Grittner wrote:
My take was more that MVs would often be refreshed by crontab, and
that you would want to keep subsequent steps from running and
generating potentially plausible but completely inaccurate results
if the LMV failed.If one of these subsequent steps doesn't care if refresh
failed once, it shouldn't be forced to fail. I imagine
that for many applications yesterday's data can be good enough.Those that care should check the timestamp.
It sounds like you and I are in agreement on this; I just didn't
state it very precisely. If a LMV on a MV which already has data
fails, the relisvalid would not prevent it from being used -- it
would be stale, but still valid data from *some* point in time. The
point is that if an MV is created WITH NO DATA or has been TRUNCATEd
and there has not been a subsequent LMV, what it contains may not
represent any state which was *ever* valid, or it may represent a
state which would only have been valid hundreds of years in the past,
had the system been computerized at that time. To me, that does not
seem like the same thing as a simple "stale" state.
I'm looking at whether there is some reasonable way to detect invalid
data as well as capture age of data. Every solution I've thought of
so far has at least one hard-to-solve race condition, but I have
hopes that I can either solve that for one of the ideas, or come up
with an idea which falls more gracefully under MVCC management.
-Kevin
Import Notes
Resolved by subject fallback
Simon Riggs wrote:
This seems very similar to the REPLACE command we discussed
earlier, except this is restricted to Mat Views.
I don't remember that discussion -- do you have a reference?
If we're going to have this, I would prefer a whole command.
e.g. REPLACE matviewname REFRESH
that would also allow
REPLACE tablename AS query
Same thing under the covers, just more widely applicable and thus
more useful.
An interesting throught. I would have thought that if we were going
to allow changing the definition of an existing MV, we would be
better off with CREATE OR REPLACE MATERIALIZED VIEW. Either way, if
you allow the column types or the number of columns to be changed,
you do tend to run into issues if there are other MVs, views,
triggers, rules, etc., which depend on the MV, so I don't think it's
material for an initial patch. But it is worth considering which way
we might want to extend it.
Either way, I don't much like overloading the use of LOAD, which
already has a very different meaning.
Well, it's hard to avoid creating new keywords without overloading
the meaning of exsiting ones. Personally I didn't find
LOAD MATERIALIZED VIEW matview_name;
to be very easy to confuse with
LOAD 'filename';
But that's a subjective thing. If too many people find that
confusing, it may be worth creating a new keyword; but I wanted to
see whether it was really necessary first.
-Kevin
Import Notes
Resolved by subject fallback
"Kevin Grittner" <kgrittn@mail.com> writes:
Simon Riggs wrote:
Either way, I don't much like overloading the use of LOAD, which
already has a very different meaning.
Well, it's hard to avoid creating new keywords without overloading
the meaning of exsiting ones.
FWIW, I'd much rather see us overload LOAD (which is seldom used)
than REPLACE (which might in the future become a widely-used DML
command).
regards, tom lane
Kevin,
I'm looking at whether there is some reasonable way to detect invalid
data as well as capture age of data. Every solution I've thought of
so far has at least one hard-to-solve race condition, but I have
hopes that I can either solve that for one of the ideas, or come up
with an idea which falls more gracefully under MVCC management.
What's the race condition? I'd think that LOAD would take an exclusive
lock on the matview involved.
LOAD MATERIALIZED VIEW matview_name;
to be very easy to confuse with
LOAD 'filename';
But that's a subjective thing. If too many people find that
confusing, it may be worth creating a new keyword; but I wanted to
see whether it was really necessary first.
I do not find them confusing.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
On 11/19/12 9:57 AM, Josh Berkus wrote:
Kevin,
I'm looking at whether there is some reasonable way to detect invalid
data as well as capture age of data. Every solution I've thought of
so far has at least one hard-to-solve race condition, but I have
hopes that I can either solve that for one of the ideas, or come up
with an idea which falls more gracefully under MVCC management.What's the race condition? I'd think that LOAD would take an exclusive
lock on the matview involved.
BTW, another thought on the timestamp: while it would be better to have
a lastrefresh timestamp in pg_class, the other option is to have an
extra column in the matview (pg_last_update). While that would involve
some redundant storage, it would neatly solve the issues around unlogged
matviews; the timestamp and the data would vanish at the same time.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
Tom Lane wrote:
"Kevin Grittner" <kgrittn@mail.com> writes:
Josh Berkus wrote:
What use would a temporary matview be?
It would be essentially like a temporary table, with all the same
persistence options. I'm not really sure how often it will be more
useful than a temporary table before we have incremental
maintenance of materialized views; once we have that, though, it
seems likely that there could be reasonable use cases.One of the principal attributes of a temp table is that its
contents aren't (reliably) accessible from anywhere except the
owning backend. Not sure where you're going to hide the incremental
maintenance in that scenario.
The more I think about that, the less sensible temporary MVs seem.
Unless I can figure out some reasonable use case, I'll diable that in
the next version of the patch.
-Kevin
Import Notes
Resolved by subject fallback
Hi Kevin,
On 15/11/2012 03:28, Kevin Grittner wrote:
Attached is a patch that is still WIP but that I think is getting
pretty close to completion.
I've been looking at this, but I unfortunately haven't had as much time
as I had hoped for, and have not looked at the code in detail yet. It's
also a relatively big patch, so I wouldn't mind another pair of eyes on it.
I have been testing the patch a bit, and I'm slightly disappointed by
the fact that it still doesn't solve this problem (and I apologize if I
have missed discussion about this in the docs or in this thread):
<assume "foo" is a non-empty materialized view>
T1: BEGIN;
T1: LOAD MATERIALIZED VIEW foo;
T2: SELECT * FROM foo;
T1: COMMIT;
<T2 sees an empty table>
As others have pointed out, replacing the contents of a table is
something which people have been wanting to do for a long time, and I
think having this ability would make this patch a lot better; now it
just feels like syntactic sugar.
1. CREATE MATERIALIZED VIEW syntax is stolen directly from CREATE
TABLE AS, with all the same clauses supported. That includes
declaring a materialized view to be temporary or unlogged.
2. MVs don't support inheritance.
3. MVs can't define foreign keys.
4. MVs can't be the target of foreign keys.
5. MVs can't have triggers.
6. Users can't create rules which reference MVs (although MVs
[ab]use the rules mechanism internally, similar to how views do).
7. MVs can't be converted to views, nor vice versa.
8. Users may not directly use INSERT/UPDATE/DELETE on an MV.
9. MVs can't directly be used in a COPY statement, but can be the
source of data using a SELECT.
10. MVs can't own sequences.
11. MVs can't be the target of LOCK statements, although other
statements get locks just like a table.
12. MVs can't use data modifying CTEs in their definitions.
13. pg_class now has a relisvalid column, which is true if an MV is
truncated or created WITH NO DATA. You can not scan a relation
flagged as invalid.
14. ALTER MATERIALIZED VIEW is supported for the options that seemed
to make sense. For example, you can change the tablespace or
schema, but you cannot add or drop column with ALTER.
16. To get new data into the MV, the command is LOAD MATERIALIZED
VIEW mat view_name. This seemed more descriptive to me that the
alternatives and avoids declaring any new keywords beyond
MATERIALIZED. If the MV is flagged as relisvalid == false, this
will change it to true.
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction, it didn't seem to make any sense to try to
apply SERIALIZABLE transaction semantics to queries looking at
the contents of an MV, although if LMV is run in a SERIALIZABLE
transaction the MV data is guaranteed to be free of serialization
anomalies. This does leave the transaction running the LOAD
command vulnerable to serialization failures unless it is also
READ ONLY DEFERRABLE.
18. Bound parameters are not supported for the CREATE MATERIALIZED
VIEW statement.
I believe all of these points have been under discussion, and I don't
have anything to add to the ongoing discussions.
19. LMV doesn't show a row count. It wouldn't be hard to add, it just
seemed a little out of place to do that, when CLUSTER, etc.,
don't.
This sounds like a useful feature, but your point about CLUSTER and
friends still stands.
In the long term, we will probably need to separate the
implementation of CREATE TABLE AS and CREATE MATERIALIZED VIEW, but
for now there is so little that they need to do differently it seemed
less evil to have a few "if" clauses that that much duplicated code.
Seems sensible.
I'll get back when I manage to get a better grasp of the code.
Regards,
Marko Tiikkaja
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Nov 25, 2012 at 7:30 PM, Marko Tiikkaja <pgmail@joh.to> wrote:
As others have pointed out, replacing the contents of a table is something
which people have been wanting to do for a long time, and I think having
this ability would make this patch a lot better; now it just feels like
syntactic sugar.
I agree that it's mostly syntactic sugar, but I think we need to have
realistic expectations for what is possible in an initial patch. When
I committed the first patch for foreign data wrappers, it didn't work
at all: it was just syntax support. Tom later committed a follow-on
patch that made them work. Similarly, I split the event trigger patch
into two halves, one of which added the syntax support and the other
of which made them functional: and even with both commits in, I think
it's fair to say that event triggers are still in a fairly primitive
state.
None of those patches were small patches. It's going to take multiple
years to get materialized views up to a state where they're really
useful to a broad audience in production applications, but I don't
think we should sneer at anyone for writing a patch that is "just
syntactic sugar". As it turns out, adding a whole new object type is
a lot of work and generates a big patch even if it doesn't do much
just yet. Rejecting such patches on the grounds that they aren't
comprehensive enough is, IMHO, extremely unwise; we'll either end up
landing even larger patches that are almost impossible to review
comprehensively and therefore more likely to break something, or else
we'll kill the projects outright and end up with nothing.
--
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
On 11/26/12 2:07 PM, Robert Haas wrote:
On Sun, Nov 25, 2012 at 7:30 PM, Marko Tiikkaja <pgmail@joh.to> wrote:
As others have pointed out, replacing the contents of a table is something
which people have been wanting to do for a long time, and I think having
this ability would make this patch a lot better; now it just feels like
syntactic sugar.I agree that it's mostly syntactic sugar, but I think we need to have
realistic expectations for what is possible in an initial patch. When
I committed the first patch for foreign data wrappers, it didn't work
at all: it was just syntax support. Tom later committed a follow-on
patch that made them work. Similarly, I split the event trigger patch
into two halves, one of which added the syntax support and the other
of which made them functional: and even with both commits in, I think
it's fair to say that event triggers are still in a fairly primitive
state.None of those patches were small patches. It's going to take multiple
years to get materialized views up to a state where they're really
useful to a broad audience in production applications, but I don't
think we should sneer at anyone for writing a patch that is "just
syntactic sugar". As it turns out, adding a whole new object type is
a lot of work and generates a big patch even if it doesn't do much
just yet. Rejecting such patches on the grounds that they aren't
comprehensive enough is, IMHO, extremely unwise; we'll either end up
landing even larger patches that are almost impossible to review
comprehensively and therefore more likely to break something, or else
we'll kill the projects outright and end up with nothing.
First of all, I have to apologize. Re-reading the email I sent out last
night, it does indeed feel a bit harsh and I can understand your reaction.
At no point did I mean to belittle Kevin's efforts or the patch itself.
I was mostly looking for Kevin's input on how hard it would be to
solve the particular problem and whether it would be possible to do so
for 9.3.
While I feel like the problem I pointed out is a small caveat and should
be at least documented for 9.3, I think this patch has merits of its own
even if that problem never gets fixed, and I will continue to review
this patch.
Regards,
Marko Tiikkaja
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 26 November 2012 13:07, Robert Haas <robertmhaas@gmail.com> wrote:
None of those patches were small patches. It's going to take multiple
years to get materialized views up to a state where they're really
useful to a broad audience in production applications, but I don't
think we should sneer at anyone for writing a patch that is "just
syntactic sugar".
+1. I have a sweet tooth. I don't like it when people criticise
patches on the basis of "obviously you could achieve the same effect
with $CONVOLUTION". Making things simpler is a desirable outcome. Now,
that isn't to say that we should disregard everything or even anything
else in pursuit of simplicity; just that "needing a Ph.D is
Postgresology", as you once put it, to do something routine to many is
really hard to defend.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11/14/12 9:28 PM, Kevin Grittner wrote:
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction,
So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should be
called then.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 26, 2012 at 8:14 AM, Marko Tiikkaja <pgmail@joh.to> wrote:
First of all, I have to apologize. Re-reading the email I sent out last
night, it does indeed feel a bit harsh and I can understand your reaction.At no point did I mean to belittle Kevin's efforts or the patch itself. I
was mostly looking for Kevin's input on how hard it would be to solve the
particular problem and whether it would be possible to do so for 9.3.While I feel like the problem I pointed out is a small caveat and should be
at least documented for 9.3, I think this patch has merits of its own even
if that problem never gets fixed, and I will continue to review this patch.
OK, no worries. I didn't really interpret your email as belittling; I
just want to make sure this feature doesn't get feature-creeped to
death. I think everyone, including Kevin, understands that the
real-world applicability of v1 is going to be limited and many people
will choose alternative techniques rather than relying on this new
feature. But I also think that we'll never get to a really awesome,
kick-ass feature unless we're willing to commit an initial version
that isn't all that awesome or kick-ass. If I understand Kevin's
goals correctly, the plan is to get this basic version committed for
9.3, and then to try to expand the capability during the 9.4 release
cycle (and maybe 9.5, too, there's a lot of work to do here). I think
that's a pretty sound plan.
--
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
On 11/26/2012 09:46 AM, Peter Eisentraut wrote:
On 11/14/12 9:28 PM, Kevin Grittner wrote:
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction,So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should be
called then.
If you use Jonathan Gardner's taxonomy at
<http://tech.jonathangardner.net/wiki/PostgreSQL/Materialized_Views>,
snapshots are a subclass of materialized views.
cheers
andrew
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 26, 2012 at 09:46:33AM -0500, Peter Eisentraut wrote:
On 11/14/12 9:28 PM, Kevin Grittner wrote:
17. Since the data viewed in an MV is not up-to-date with the
latest committed transaction,So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should be
called then.
"Snapshot" is one of the options for refreshing an Oracle materialized
view. There are others, which we'll eventually add if past is any
prologue :)
I hate to add to the bike-shedding, but we should probably add REFRESH
SNAPSHOT as an optional piece of the grammar, with more REFRESH
options to come.
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
On 26 November 2012 15:24, David Fetter <david@fetter.org> wrote:
I hate to add to the bike-shedding, but we should probably add REFRESH
SNAPSHOT as an optional piece of the grammar, with more REFRESH
options to come.
I don't know that they should be called materialised views, but do we
really need to overload the word snapshot? I'd just as soon invent a
new word as use the Oracle one, since I don't think the term snapshot
is widely recognised as referring to anything other than snapshot
isolation.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 26, 2012 at 04:02:17PM +0000, Peter Geoghegan wrote:
On 26 November 2012 15:24, David Fetter <david@fetter.org> wrote:
I hate to add to the bike-shedding, but we should probably add
REFRESH SNAPSHOT as an optional piece of the grammar, with more
REFRESH options to come.I don't know that they should be called materialised views, but do
we really need to overload the word snapshot? I'd just as soon
invent a new word as use the Oracle one, since I don't think the
term snapshot is widely recognised as referring to anything other
than snapshot isolation.
I believe that the meaning here is unambiguous, and is used in other
descriptions than Oracle's, including the one on our wiki.
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
Marko Tiikkaja wrote:
On 15/11/2012 03:28, Kevin Grittner wrote:
I have been testing the patch a bit
Thanks!
and I'm slightly disappointed by the fact that it still doesn't
solve this problem (and I apologize if I have missed discussion
about this in the docs or in this thread):<assume "foo" is a non-empty materialized view>
T1: BEGIN;
T1: LOAD MATERIALIZED VIEW foo;T2: SELECT * FROM foo;
T1: COMMIT;
<T2 sees an empty table>
As far as I know you are the first to notice this behavior. Thanks
for pointing it out.
I agree with Robert that we have to be careful about scope creep,
but this one looks to me like it should be considered a bug. IMO,
LOAD for a populated MV should move it from one state which
reflects the captured state of a previous point in time to a
captured state which is later, with no invalid or inconsistent
states visible in between.
I will take a look at the issue; I don't know whether it's
something small I can address in this CF or whether it will need to
be in the next CF, but I will fix it.
19. LMV doesn't show a row count. It wouldn't be hard to add, it
just seemed a little out of place to do that, when CLUSTER,
etc., don't.This sounds like a useful feature, but your point about CLUSTER
and friends still stands.
Other possible arguments against providing a count are:
(1) For a populated MV, the LOAD might be replacing the contents
with fewer rows than were there before.
(2) Once we have incremental updates of the MV, this count is only
one of the ways to update the view -- and the others won't show
counts. Showing it here might be considered inconsistent.
I don't feel strongly about it, and I don't think it's a big change
either way; just explaining what got me off the fence when I had to
pick one behavior or the other to post the WIP patch.
I'll get back when I manage to get a better grasp of the code.
Thanks.
Keep in mind that the current behavior of behaving like a regular
view when the contents are invalid is not what I had in mind, that
was an accidental effect of commenting out the body of the
ExecCheckRelationsValid() function right before posting the patch
because I noticed a regression. When I noticed current behavior, it
struck me that someone might prefer it to the intended behavior of
showing an error like this:
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("materialized view \"%s\" has not been populated",
get_rel_name(rte->relid)),
errhint("Use the LOAD MATERIALIZED VIEW command.")));
I mention it in case someone wants to argue for silently behaving
as a regular view when the MV is not populated.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback
David Fetter wrote:
On Mon, Nov 26, 2012 at 09:46:33AM -0500, Peter Eisentraut wrote:
So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should
be called then."Snapshot" is one of the options for refreshing an Oracle
materialized view. There are others, which we'll eventually add
if past is any prologue :)
That's the way I understand it, too.
I hate to add to the bike-shedding, but we should probably add
REFRESH SNAPSHOT as an optional piece of the grammar, with more
REFRESH options to come.
I would prefer to leave the syntax for refreshing MVs to a later
patch. I'm kind of assuming that SNAPSHOT would be the default if
no other options are specified, but I would prefer not to get into
too much speculation about add-on patches for fear of derailing
this initial effort.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback
On Mon, Nov 26, 2012 at 04:34:36PM -0500, Kevin Grittner wrote:
David Fetter wrote:
On Mon, Nov 26, 2012 at 09:46:33AM -0500, Peter Eisentraut wrote:
So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should
be called then."Snapshot" is one of the options for refreshing an Oracle
materialized view. There are others, which we'll eventually add
if past is any prologue :)That's the way I understand it, too.
Great :)
I hate to add to the bike-shedding, but we should probably add
REFRESH SNAPSHOT as an optional piece of the grammar, with more
REFRESH options to come.I would prefer to leave the syntax for refreshing MVs to a later
patch. I'm kind of assuming that SNAPSHOT would be the default if
no other options are specified, but I would prefer not to get into
too much speculation about add-on patches for fear of derailing
this initial effort.
You're right. I withdraw my suggestion until such time as this patch
(or descendent) is committed and actual working code implementing
other refresh strategies is written.
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
On Mon, 2012-11-26 at 09:46 -0500, Peter Eisentraut wrote:
On 11/14/12 9:28 PM, Kevin Grittner wrote:
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction,So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should be
called then.
OK, I take everything back and claim the opposite.
In current Oracle, SNAPSHOT is an obsolete alias for MATERIALIZED VIEW.
Materialized views have the option of REFRESH ON DEMAND and REFRESH ON
COMMIT, with the former being the default. So it seems that the syntax
of what you are proposing is in line with Oracle.
I'm not fond of overloading LOAD as the refresh command. Maybe you
could go the Oracle route here as well and use a stored procedure. That
would also allow things like SELECT pg_refresh_mv(oid) FROM ... more
easily.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-
owner@postgresql.org] On Behalf Of Peter Eisentraut
Sent: 27 November 2012 13:35
To: Kevin Grittner
Cc: Pgsql Hackers
Subject: Re: [HACKERS] Materialized views WIP patchOn Mon, 2012-11-26 at 09:46 -0500, Peter Eisentraut wrote:
On 11/14/12 9:28 PM, Kevin Grittner wrote:
17. Since the data viewed in an MV is not up-to-date with the latest
committed transaction,So, the way I understand it, in Oracle terms, this feature is a
"snapshot", not a materialized view. Maybe that's what it should be
called then.OK, I take everything back and claim the opposite.
In current Oracle, SNAPSHOT is an obsolete alias for MATERIALIZED VIEW.
Materialized views have the option of REFRESH ON DEMAND and REFRESH
ON COMMIT, with the former being the default. So it seems that the syntax
of what you are proposing is in line with Oracle.I'm not fond of overloading LOAD as the refresh command. Maybe you could
go the Oracle route here as well and use a stored procedure. That would also
allow things like SELECT pg_refresh_mv(oid) FROM ... more easily.
+1 to this.
I can see a use case where you might want to refresh all MVs that are X number of days/hours old. Rather than having to execute statements for each one. Something like pg_refresh_mv() within a query would allow this.
Pretty exciting work Kevin, I understand what Robert said about feature creep etc and agree 100%, but I'm really looking forward to when we can *one day* have the planner make use of an eager MV to optimise a query!
Regards
David Rowley
--
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
Peter Eisentraut <peter_e@gmx.net> writes:
I'm not fond of overloading LOAD as the refresh command. Maybe you
could go the Oracle route here as well and use a stored procedure. That
would also allow things like SELECT pg_refresh_mv(oid) FROM ... more
easily.
I would like that we have a way to refresh a Materialized View by
calling a stored procedure, but I don't think it should be the main UI.
The wholesale refreshing of a matview appears to me to be comparable to
TRUNCATE is that it's both a DDL and a DML. The incremental refreshing
modes we want to have later are clearly DML only, either on commit
refresh or incrementally on demand.
I would then propose that we use ALTER MATERIALIZED VIEW as the UI for
the wholesale on demand refreshing operation, and UPDATE MATERIALIZED
VIEW as the incremental command (to come later).
So my proposal for the current feature would be:
ALTER MATERIALIZED VIEW mv UPDATE [ CONCURRENTLY ];
UPDATE MATERIALIZED VIEW mv;
The choice of keywords and syntax here hopefully clearly hint the user
about the locking behavior of the commands, too. And as we said, the
bare minimum for this patch does *not* include the CONCURRENTLY option,
which we still all want to have (someday). :)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/11/27 Dimitri Fontaine <dimitri@2ndquadrant.fr>:
Peter Eisentraut <peter_e@gmx.net> writes:
I'm not fond of overloading LOAD as the refresh command. Maybe you
could go the Oracle route here as well and use a stored procedure. That
would also allow things like SELECT pg_refresh_mv(oid) FROM ... more
easily.I would like that we have a way to refresh a Materialized View by
calling a stored procedure, but I don't think it should be the main UI.The wholesale refreshing of a matview appears to me to be comparable to
TRUNCATE is that it's both a DDL and a DML. The incremental refreshing
modes we want to have later are clearly DML only, either on commit
refresh or incrementally on demand.I would then propose that we use ALTER MATERIALIZED VIEW as the UI for
the wholesale on demand refreshing operation, and UPDATE MATERIALIZED
VIEW as the incremental command (to come later).So my proposal for the current feature would be:
ALTER MATERIALIZED VIEW mv UPDATE [ CONCURRENTLY ];
UPDATE MATERIALIZED VIEW mv;The choice of keywords and syntax here hopefully clearly hint the user
about the locking behavior of the commands, too. And as we said, the
bare minimum for this patch does *not* include the CONCURRENTLY option,
which we still all want to have (someday). :)
+1
Pavel
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support--
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
Pavel Stehule wrote:
2012/11/27 Dimitri Fontaine <dimitri@2ndquadrant.fr>:
I would like that we have a way to refresh a Materialized View
by calling a stored procedure, but I don't think it should be
the main UI.
I agree. I saw that Oracle uses a function for that without any
statement-level support, and that would probably be easier to
implement; but it felt wrong to do it that way. I couldn't think of
any other cases where similar action is taken without statement
syntax for it.
The wholesale refreshing of a matview appears to me to be
comparable to TRUNCATE is that it's both a DDL and a DML. The
incremental refreshing modes we want to have later are clearly
DML only, either on commit refresh or incrementally on demand.
Personally, I expect the most popular update method to eventually
become a queued update. I've looked ahead far enough to see that I
want to structure the incremental updates to be controlled through
an API where changes to supporting tables produce records saying
what was done which are fed to consumers which do the updating.
Then it becomes a matter of whether that consumer performs the
related updates to the MV during commit processing of the producer,
by pulling from a queue, or by reading accumulated rows when the MV
is referenced.
But I'm getting ahead of things with such speculation...
I would then propose that we use ALTER MATERIALIZED VIEW as the
UI for the wholesale on demand refreshing operation, and UPDATE
MATERIALIZED VIEW as the incremental command (to come later).
Honestly, I have managed to keep myself from speculating on syntax
for incremental updates. There will be enough complexity involved
that I expect months of bikeshedding. :-/
So my proposal for the current feature would be:
ALTER MATERIALIZED VIEW mv UPDATE [ CONCURRENTLY ];
UPDATE MATERIALIZED VIEW mv;
An ALTER MATERIALIZED VIEW option was my first thought on syntax to
do what LOAD does in the current patch. But it bothered me that I
couldn't think of any other cases where ALTER <some-object-type>
only changed the data contained within the object and had no other
impact. Are you both really comfortable with an ALTER MATERIALIZED
VIEW which has no effect other than to update the data? It seems
wrong to me.
The choice of keywords and syntax here hopefully clearly hint
the user about the locking behavior of the commands, too. And as
we said, the bare minimum for this patch does *not* include the
CONCURRENTLY option, which we still all want to have (someday).
:)+1
Sure -- a CONCURRENTLY option for LMV (or AMVU) seems like one of
the next steps. I'll feel more confident about implementing that
when it appears that we have shaken the last bugs out of
CREATE/DROP INDEX CONCURRENTLY, since anything which affects those
statements will probably also matter here.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback
On Nov 27, 2012, at 5:25, Dimitri Fontaine <dimitri@2ndQuadrant.fr> wrote:
So my proposal for the current feature would be:
ALTER MATERIALIZED VIEW mv UPDATE [ CONCURRENTLY ];
UPDATE MATERIALIZED VIEW mv;The choice of keywords and syntax here hopefully clearly hint the user
about the locking behavior of the commands, too. And as we said, the
bare minimum for this patch does *not* include the CONCURRENTLY option,
which we still all want to have (someday). :)
I dislike using ALTER syntax to perform a data-only action.
The other advantage of non-functional syntax is that you could more easily supply some form of where clause should you only want to perform a partial refresh. With a function call that becomes more obtuse.
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"Kevin Grittner" <kgrittn@mail.com> writes:
An ALTER MATERIALIZED VIEW option was my first thought on syntax to
do what LOAD does in the current patch. But it bothered me that I
couldn't think of any other cases where ALTER <some-object-type>
only changed the data contained within the object and had no other
impact. Are you both really comfortable with an ALTER MATERIALIZED
VIEW which has no effect other than to update the data? It seems
wrong to me.
I think you can already do that with some clever use of alter table ...
type using, or alter table set default.
Sure -- a CONCURRENTLY option for LMV (or AMVU) seems like one of
the next steps. I'll feel more confident about implementing that
when it appears that we have shaken the last bugs out of
CREATE/DROP INDEX CONCURRENTLY, since anything which affects those
statements will probably also matter here.
Sure.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine wrote:
"Kevin Grittner" <kgrittn@mail.com> writes:
An ALTER MATERIALIZED VIEW option was my first thought on syntax
to do what LOAD does in the current patch. But it bothered me
that I couldn't think of any other cases where ALTER
<some-object-type> only changed the data contained within the
object and had no other impact. Are you both really comfortable
with an ALTER MATERIALIZED VIEW which has no effect other than
to update the data? It seems wrong to me.I think you can already do that with some clever use of alter
table ... type using, or alter table set default.
You mean, specifying an ALTER TABLE which appears to specify a
change to some non-data aspect of a table but which is really
setting it to the existing state? And it therefore rewrites the
table? I suppose that with USING you could actually even have it
rewritten with data different from what was there before without
changing the structure of the table. Somehow I don't find that
pursuasive as an argument for what ALTER MATERIALIZED VIEW should
rescan the source relations and build a whole new set of data for
exactly the same MV definition.
Consider that in relational theory a table is considered a relation
variable. ALTER is supposed to change the definition of the
variable in some way. Other statements are used to change the value
contained in the variable. Sure there are some grey areas already,
but I don't see where we need to muddy the waters in this case.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback
"Kevin Grittner" <kgrittn@mail.com> writes:
changing the structure of the table. Somehow I don't find that
pursuasive as an argument for what ALTER MATERIALIZED VIEW should
rescan the source relations and build a whole new set of data for
exactly the same MV definition.
Fair enough.
Consider that in relational theory a table is considered a relation
variable. ALTER is supposed to change the definition of the
variable in some way. Other statements are used to change the value
contained in the variable. Sure there are some grey areas already,
but I don't see where we need to muddy the waters in this case.
Under that light, using ALTER is strange indeed. I still don't like
using LOAD that much, allow me to try a last syntax proposal. Well all I
can find just now would be:
UPDATE MATERIALIZED VIEW mv FOR EACH ROW;
UPDATE MATERIALIZED VIEW mv FOR EACH STATEMENT [ CONCURRENTLY ];
The only value of such a proposal is that it's not LOAD and it's still
not introducing any new keyword. Oh it's also avoiding to overload the
SNAPSHOT keyword. Well, it still does not look like the best candidate.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Under that light, using ALTER is strange indeed.
Agreed, seems like a poor choice.
I still don't like
using LOAD that much, allow me to try a last syntax proposal. Well all I
can find just now would be:
UPDATE MATERIALIZED VIEW mv FOR EACH ROW;
UPDATE MATERIALIZED VIEW mv FOR EACH STATEMENT [ CONCURRENTLY ];
The only value of such a proposal is that it's not LOAD and it's still
not introducing any new keyword. Oh it's also avoiding to overload the
SNAPSHOT keyword. Well, it still does not look like the best candidate.
I think this syntax would require making MATERIALIZED (and possibly also
VIEW) fully reserved keywords, which would be better avoided.
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
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-
owner@postgresql.org] On Behalf Of Dimitri Fontaine
Sent: Tuesday, November 27, 2012 10:03 AM
To: Kevin Grittner
Cc: Pavel Stehule; Peter Eisentraut; Pgsql Hackers
Subject: Re: [HACKERS] Materialized views WIP patch"Kevin Grittner" <kgrittn@mail.com> writes:
changing the structure of the table. Somehow I don't find that
pursuasive as an argument for what ALTER MATERIALIZED VIEW should
rescan the source relations and build a whole new set of data for
exactly the same MV definition.Fair enough.
Consider that in relational theory a table is considered a relation
variable. ALTER is supposed to change the definition of the variable
in some way. Other statements are used to change the value contained
in the variable. Sure there are some grey areas already, but I don't
see where we need to muddy the waters in this case.Under that light, using ALTER is strange indeed. I still don't like using
LOAD
that much, allow me to try a last syntax proposal. Well all I can find
just now
would be:
UPDATE MATERIALIZED VIEW mv FOR EACH ROW;
UPDATE MATERIALIZED VIEW mv FOR EACH STATEMENT [ CONCURRENTLY ];The only value of such a proposal is that it's not LOAD and it's still not
introducing any new keyword. Oh it's also avoiding to overload the
SNAPSHOT keyword. Well, it still does not look like the best candidate.Regards,
Just a thought but how about something like:
DO REFRESH OF MATERIALIZED VIEW mat_view;
In effect we begin overloading the meaning of "DO" to not only mean
anonymous code blocks but to also call pre-defined internal routines that
can be executed without having to use function-call syntax. "MATERIALIZED
VIEW" can be more generic "e.g., TABLE" if the need arises, the REFRESH
"Action" is generic, and additional clauses can be added after the object
name (FOR, CONCURRENTLY, WHERE, etc...)
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/11/27 David Johnston <polobo@yahoo.com>:
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-
owner@postgresql.org] On Behalf Of Dimitri Fontaine
Sent: Tuesday, November 27, 2012 10:03 AM
To: Kevin Grittner
Cc: Pavel Stehule; Peter Eisentraut; Pgsql Hackers
Subject: Re: [HACKERS] Materialized views WIP patch"Kevin Grittner" <kgrittn@mail.com> writes:
changing the structure of the table. Somehow I don't find that
pursuasive as an argument for what ALTER MATERIALIZED VIEW should
rescan the source relations and build a whole new set of data for
exactly the same MV definition.Fair enough.
Consider that in relational theory a table is considered a relation
variable. ALTER is supposed to change the definition of the variable
in some way. Other statements are used to change the value contained
in the variable. Sure there are some grey areas already, but I don't
see where we need to muddy the waters in this case.Under that light, using ALTER is strange indeed. I still don't like using
LOAD
that much, allow me to try a last syntax proposal. Well all I can find
just now
would be:
UPDATE MATERIALIZED VIEW mv FOR EACH ROW;
UPDATE MATERIALIZED VIEW mv FOR EACH STATEMENT [ CONCURRENTLY ];The only value of such a proposal is that it's not LOAD and it's still not
introducing any new keyword. Oh it's also avoiding to overload the
SNAPSHOT keyword. Well, it still does not look like the best candidate.Regards,
Just a thought but how about something like:
DO REFRESH OF MATERIALIZED VIEW mat_view;
In effect we begin overloading the meaning of "DO" to not only mean
anonymous code blocks but to also call pre-defined internal routines that
can be executed without having to use function-call syntax. "MATERIALIZED
VIEW" can be more generic "e.g., TABLE" if the need arises, the REFRESH
"Action" is generic, and additional clauses can be added after the object
name (FOR, CONCURRENTLY, WHERE, etc...)
-1
I unlike using keywords DO for this purpose - when we use it for
anonymous blocks
Regards
Pavel
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Nov 27, 2012 at 10:58 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
I unlike using keywords DO for this purpose - when we use it for
anonymous blocks
Yeah, I don't much like that either. My original suggestion when
Kevin and I discussed this over voice was ALTER MATERIALIZED VIEW ..
REFRESH or ALTER MATERIALIZED VIEW .. UPDATE. I don't particularly
like syntaxes involving DO or LOAD because those words already have
strong associations with completely unrelated features. Now, if we
don't want to do that and we don't want to use ALTER for a
data-modifying command either, another option would be to invent a new
toplevel command:
REFRESH <view_name>;
Of course, that does introduce another keyword, but the penalty for a
new unreserved keyword is pretty small. It seems like a rough
analogue of CLUSTER, which could be spelled ALTER TABLE <table_name>
UPDATE TABLE ORDER TO if keyword minimization trumped both concision
and clarity, but it doesn't.
--
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
Robert Haas wrote:
I don't particularly like syntaxes involving DO or LOAD because
those words already have strong associations with completely
unrelated features. Now, if we don't want to do that and we don't
want to use ALTER for a data-modifying command either, another
option would be to invent a new toplevel command:REFRESH <view_name>;
Of course, that does introduce another keyword, but the penalty
for a new unreserved keyword is pretty small.
Of the alternatives to LOAD MATERIALIZED VIEW, something involving
REFRESH seems the best to me. The question is whether REFRESH
MATERIALIZED VIEW (or just REFRESH) is more clear, and whether it
is so by enough to merit another keyword. Of course, there is a
chance that we may wind up needing that keyword for declaring
incremental updates anyway, so it might be a matter of *when* we do
it rather than *whether* we do it -- depending on the yet-to-be-
determined syntax for specifying incremental updates.
My personal preference is still for LOAD MATERIALIZED VIEW because
it implies a complete regeneration rather than something more
incremental, but I realize that is subjective.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback
2012/11/28 Kevin Grittner <kgrittn@mail.com>:
Robert Haas wrote:
I don't particularly like syntaxes involving DO or LOAD because
those words already have strong associations with completely
unrelated features. Now, if we don't want to do that and we don't
want to use ALTER for a data-modifying command either, another
option would be to invent a new toplevel command:REFRESH <view_name>;
Of course, that does introduce another keyword, but the penalty
for a new unreserved keyword is pretty small.Of the alternatives to LOAD MATERIALIZED VIEW, something involving
REFRESH seems the best to me. The question is whether REFRESH
MATERIALIZED VIEW (or just REFRESH) is more clear, and whether it
is so by enough to merit another keyword. Of course, there is a
chance that we may wind up needing that keyword for declaring
incremental updates anyway, so it might be a matter of *when* we do
it rather than *whether* we do it -- depending on the yet-to-be-
determined syntax for specifying incremental updates.My personal preference is still for LOAD MATERIALIZED VIEW because
it implies a complete regeneration rather than something more
incremental, but I realize that is subjective.
In this context I prefer REFRESH keyword - I have a LOAD associated
with BULKLOAD, a this is different
Regards
Pavel
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi Kevin,
On Mon, 26 Nov 2012 22:24:33 +0100, Kevin Grittner <kgrittn@mail.com>
wrote:
Marko Tiikkaja wrote:
<T2 sees an empty table>
As far as I know you are the first to notice this behavior. Thanks
for pointing it out.I will take a look at the issue; I don't know whether it's
something small I can address in this CF or whether it will need to
be in the next CF, but I will fix it.
Any news on this front?
I'll get back when I manage to get a better grasp of the code.
The code looks relatively straightforward and good to my eyes. It passes
my testing and looks to be changing all the necessary parts of the code.
Keep in mind that the current behavior of behaving like a regular
view when the contents are invalid is not what I had in mind, that
was an accidental effect of commenting out the body of the
ExecCheckRelationsValid() function right before posting the patch
because I noticed a regression. When I noticed current behavior, it
struck me that someone might prefer it to the intended behavior of
showing an error like this:ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("materialized view \"%s\" has not been populated",
get_rel_name(rte->relid)),
errhint("Use the LOAD MATERIALIZED VIEW command.")));I mention it in case someone wants to argue for silently behaving
as a regular view when the MV is not populated.
FWIW, I'd prefer an error in this case, but I don't feel strongly about it.
Regards,
Marko Tiikkaja
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Marko Tiikkaja wrote:
Kevin Grittner <kgrittn@mail.com> wrote:
Marko Tiikkaja wrote:
<T2 sees an empty table>
As far as I know you are the first to notice this behavior.
Thanks for pointing it out.I will take a look at the issue; I don't know whether it's
something small I can address in this CF or whether it will need
to be in the next CF, but I will fix it.Any news on this front?
On a preliminary look, I think that it's because when the new heap
is visible, it has been populated with rows containing an xmin too
new to be visible to the transaction which was blocked. I'll see if
I can't populate the new heap with tuples which are already frozen
and hinted, which will not only fix this bug, but improve
performance.
I'll get back when I manage to get a better grasp of the code.
The code looks relatively straightforward and good to my eyes. It
passes my testing and looks to be changing all the necessary
parts of the code.
Thanks. I'll put together a new version of the patch based on
feedback so far. I need to finish my testing of the fklocks patch
and post a review first, so it will be a few days.
Keep in mind that the current behavior of behaving like a
regular view when the contents are invalid is not what I had in
mind, that was an accidental effect of commenting out the body
of the ExecCheckRelationsValid() function right before posting
the patch because I noticed a regression. When I noticed current
behavior, it struck me that someone might prefer it to the
intended behavior of showing an error like this:ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("materialized view \"%s\" has not been populated",
get_rel_name(rte->relid)),
errhint("Use the LOAD MATERIALIZED VIEW command.")));I mention it in case someone wants to argue for silently
behaving as a regular view when the MV is not populated.FWIW, I'd prefer an error in this case, but I don't feel strongly
about it.
Me, too. Since nobody else has spoken up, that's what I'll do.
I haven't heard anyone argue for keeping temporary materialized
views, so those will be dropped from the next version of the patch.
It sounds like most people prefer REFRESH to LOAD. If there is no
further discussion of that, I will make that change in the next
version of the patch; so if you want to argue against adding a new
unreserved keyword for that, now is the time to say so. Also,
should it be just?:
REFRESH matview_name;
or?:
REFRESH MATERIALIZED VIEW matview_name;
I will look at heap_inplace_update() where appropriate for the
pg_class changes.
On the issue of UHLOGGED MVs, I think it's pretty clear that the
right way to handle this is to have something in the init fork of
an unlogged MV that indicates that it is in an invalid state, which
is checked when the MV is opened. I'm not sure of the best way to
store that, but my first instinct would be to put it inot the
"special space" of the initial page, which would then be removed
after forcing the relisvalid flag to false. That feels pretty
kludgy, but its the best idea I've had so far. Anyone else want to
suggest a better way?
I will add regression tests in the next version of the patch.
While I agree that tracking one or more timestamps related to MV
"freshness" is a good idea, that seems like material for a
follow-on patch.
I don't agree that never having been loaded is a form of "stale",
in spite of multiple people whom I hold in high regard expressing
that opinion, so barring strong opposition I intend to keep the
relisvalid column. I just don't feel comfortable about the
fundamental correctness of the feature without it. If I'm reading
things correctly, so far the opposition has been based on being
lukewarm on the value of the column and wanting other features
which might be harder with this column (i.e., UNLOGGED) or which
are seen as alternatives to this column (i.e., a "freshness"
timestamp) rather than actual opposition to throwing an error on an
attempt to use an MV which has not been loaded with data.
I will fix the commented-out test of relisvalid.
I still haven't quite figured out what to do about the case Thom
uncovered where the MV's SELECT was just selecting a literal. I
haven't established what the most general case of that is, nor what
a sensible behavior is. Any opinions or insights welcome.
I didn't see any rebuttal to Tom's concerns about adding
asymetrical COPY support, so I will leave that part as is.
I will try to fill out the dependencies in pg_dump so that MVs
which depend on other MVs will be dumped in the right order.
I think that will probably be enough for the next version.
-Kevin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Import Notes
Resolved by subject fallback