mirror of https://github.com/postgres/postgres
A materialized view has a rule just like a view and a heap and other physical properties like a table. The rule is only used to populate the table, references in queries refer to the materialized data. This is a minimal implementation, but should still be useful in many cases. Currently data is only populated "on demand" by the CREATE MATERIALIZED VIEW and REFRESH MATERIALIZED VIEW statements. It is expected that future releases will add incremental updates with various timings, and that a more refined concept of defining what is "fresh" data will be developed. At some point it may even be possible to have queries use a materialized in place of references to underlying tables, but that requires the other above-mentioned features to be working first. Much of the documentation work by Robert Haas. Review by Noah Misch, Thom Brown, Robert Haas, Marko Tiikkaja Security review by KaiGai Kohei, with a decision on how best to implement sepgsql still pending.pull/3/head
parent
b15a6da292
commit
3bf3ab8c56
@ -0,0 +1,167 @@ |
||||
<!-- |
||||
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> |
||||
<replaceable class="PARAMETER">action</replaceable> [, ... ] |
||||
ALTER MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> |
||||
RENAME [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> TO <replaceable class="PARAMETER">new_column_name</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> |
||||
|
||||
<phrase>where <replaceable class="PARAMETER">action</replaceable> is one of:</phrase> |
||||
|
||||
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable> |
||||
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET ( <replaceable class="PARAMETER">attribute_option</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] ) |
||||
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> RESET ( <replaceable class="PARAMETER">attribute_option</replaceable> [, ... ] ) |
||||
ALTER [ COLUMN ] <replaceable class="PARAMETER">column_name</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN } |
||||
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable> |
||||
SET WITHOUT CLUSTER |
||||
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] ) |
||||
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] ) |
||||
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable> |
||||
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> |
||||
</synopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1> |
||||
<title>Description</title> |
||||
|
||||
<para> |
||||
<command>ALTER MATERIALIZED VIEW</command> changes various auxiliary |
||||
properties of an existing materialized view. |
||||
</para> |
||||
|
||||
<para> |
||||
You must own the materialized view to use <command>ALTER MATERIALIZED |
||||
VIEW</>. To change a materialized 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> |
||||
|
||||
<para> |
||||
The statement subforms and actions available for |
||||
<command>ALTER MATERIALIZED VIEW</command> are a subset of those available |
||||
for <command>ALTER TABLE</command>, and have the same meaning when used for |
||||
materialized views. See the descriptions for <xref linkend="sql-altertable"> |
||||
for details. |
||||
</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><replaceable class="PARAMETER">column_name</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
Name of a new or existing column. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><replaceable class="PARAMETER">new_column_name</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
New name for an existing column. |
||||
</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> |
||||
</variablelist> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Examples</title> |
||||
|
||||
<para> |
||||
To rename the materialized view <literal>foo</literal> to |
||||
<literal>bar</literal>: |
||||
<programlisting> |
||||
ALTER MATERIALIZED VIEW foo RENAME TO bar; |
||||
</programlisting></para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Compatibility</title> |
||||
|
||||
<para> |
||||
<command>ALTER MATERIALIZED VIEW</command> is a |
||||
<productname>PostgreSQL</productname> extension. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="sql-creatematerializedview"></member> |
||||
<member><xref linkend="sql-dropmaterializedview"></member> |
||||
<member><xref linkend="sql-refreshmaterializedview"></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
</refentry> |
@ -0,0 +1,154 @@ |
||||
<!-- |
||||
doc/src/sgml/ref/create_materialized_view.sgml |
||||
PostgreSQL documentation |
||||
--> |
||||
|
||||
<refentry id="SQL-CREATEMATERIALIZEDVIEW"> |
||||
<refmeta> |
||||
<refentrytitle>CREATE MATERIALIZED VIEW</refentrytitle> |
||||
<manvolnum>7</manvolnum> |
||||
<refmiscinfo>SQL - Language Statements</refmiscinfo> |
||||
</refmeta> |
||||
|
||||
<refnamediv> |
||||
<refname>CREATE MATERIALIZED VIEW</refname> |
||||
<refpurpose>define a new materialized view</refpurpose> |
||||
</refnamediv> |
||||
|
||||
<indexterm zone="sql-creatematerializedview"> |
||||
<primary>CREATE MATERIALIZED VIEW</primary> |
||||
</indexterm> |
||||
|
||||
<refsynopsisdiv> |
||||
<synopsis> |
||||
CREATE [ UNLOGGED ] MATERIALIZED VIEW <replaceable>table_name</replaceable> |
||||
[ (<replaceable>column_name</replaceable> [, ...] ) ] |
||||
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ] |
||||
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] |
||||
AS <replaceable>query</replaceable> |
||||
[ WITH [ NO ] DATA ] |
||||
</synopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1> |
||||
<title>Description</title> |
||||
|
||||
<para> |
||||
<command>CREATE MATERIALIZED VIEW</command> defines a materialized view of |
||||
a query. The query is executed and used to populate the view at the time |
||||
the command is issued (unless <command>WITH NO DATA</> is used) and may be |
||||
refreshed later using <command>REFRESH MATERIALIZED VIEW</command>. |
||||
</para> |
||||
|
||||
<para> |
||||
<command>CREATE MATERIALIZED VIEW</command> is similar to |
||||
<command>CREATE TABLE AS</>, except that it also remembers the query used |
||||
to initialize the view, so that it can be refreshed later upon demand. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Parameters</title> |
||||
|
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><literal>UNLOGGED</></term> |
||||
<listitem> |
||||
<para> |
||||
If specified, the materialized view will be unlogged. |
||||
Refer to <xref linkend="sql-createtable"> for details. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><replaceable>table_name</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
The name (optionally schema-qualified) of the materialized view to be |
||||
created. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><replaceable>column_name</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
The name of a column in the new materialized view. If column names are |
||||
not provided, they are taken from the output column names of the query. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><literal>WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )</literal></term> |
||||
<listitem> |
||||
<para> |
||||
This clause specifies optional storage parameters for the new |
||||
materialized view; see <xref linkend="sql-createtable-storage-parameters" |
||||
endterm="sql-createtable-storage-parameters-title"> for more |
||||
information. |
||||
See <xref linkend="sql-createtable"> for more information. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><literal>TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable></literal></term> |
||||
<listitem> |
||||
<para> |
||||
The <replaceable class="PARAMETER">tablespace_name</replaceable> is the name |
||||
of the tablespace in which the new materialized view is to be created. |
||||
If not specified, <xref linkend="guc-default-tablespace"> is consulted. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><replaceable>query</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
A <xref linkend="sql-select">, <link linkend="sql-table">TABLE</link>, |
||||
or <xref linkend="sql-values"> command. |
||||
</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 unscannable and cannot be queried until <command>REFRESH |
||||
MATERIALIZED VIEW</> is used. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
</variablelist> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Compatibility</title> |
||||
|
||||
<para> |
||||
<command>CREATE MATERIALIZED VIEW</command> is a |
||||
<productname>PostgreSQL</productname> extension. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="sql-altermaterializedview"></member> |
||||
<member><xref linkend="sql-createtableas"></member> |
||||
<member><xref linkend="sql-createview"></member> |
||||
<member><xref linkend="sql-dropmaterializedview"></member> |
||||
<member><xref linkend="sql-refreshmaterializedview"></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
|
||||
</refentry> |
@ -0,0 +1,114 @@ |
||||
<!-- |
||||
doc/src/sgml/ref/drop_materialized_view.sgml |
||||
PostgreSQL documentation |
||||
--> |
||||
|
||||
<refentry id="SQL-DROPMATERIALIZEDVIEW"> |
||||
<refmeta> |
||||
<refentrytitle>DROP MATERIALIZED VIEW</refentrytitle> |
||||
<manvolnum>7</manvolnum> |
||||
<refmiscinfo>SQL - Language Statements</refmiscinfo> |
||||
</refmeta> |
||||
|
||||
<refnamediv> |
||||
<refname>DROP MATERIALIZED VIEW</refname> |
||||
<refpurpose>remove a materialized view</refpurpose> |
||||
</refnamediv> |
||||
|
||||
<indexterm zone="sql-dropmaterializedview"> |
||||
<primary>DROP MATERIALIZED VIEW</primary> |
||||
</indexterm> |
||||
|
||||
<refsynopsisdiv> |
||||
<synopsis> |
||||
DROP MATERIALIZED VIEW [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ] |
||||
</synopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1> |
||||
<title>Description</title> |
||||
|
||||
<para> |
||||
<command>DROP MATERIALIZED VIEW</command> drops an existing materialized |
||||
view. To execute this command you must be the owner of the materialized |
||||
view. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Parameters</title> |
||||
|
||||
<variablelist> |
||||
<varlistentry> |
||||
<term><literal>IF EXISTS</literal></term> |
||||
<listitem> |
||||
<para> |
||||
Do not throw an error if the materialized view does not exist. A notice |
||||
is issued in this case. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><replaceable class="PARAMETER">name</replaceable></term> |
||||
<listitem> |
||||
<para> |
||||
The name (optionally schema-qualified) of the materialized view to |
||||
remove. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><literal>CASCADE</literal></term> |
||||
<listitem> |
||||
<para> |
||||
Automatically drop objects that depend on the materialized view (such as |
||||
other materialized views, or regular views). |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
|
||||
<varlistentry> |
||||
<term><literal>RESTRICT</literal></term> |
||||
<listitem> |
||||
<para> |
||||
Refuse to drop the materialized view if any objects depend on it. This |
||||
is the default. |
||||
</para> |
||||
</listitem> |
||||
</varlistentry> |
||||
</variablelist> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Examples</title> |
||||
|
||||
<para> |
||||
This command will remove the materialized view called |
||||
<literal>order_summary</literal>: |
||||
<programlisting> |
||||
DROP MATERIALIZED VIEW order_summary; |
||||
</programlisting></para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Compatibility</title> |
||||
|
||||
<para> |
||||
<command>DROP MATERIALIZED VIEW</command> is a |
||||
<productname>PostgreSQL</productname> extension. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="sql-creatematerializedview"></member> |
||||
<member><xref linkend="sql-altermaterializedview"></member> |
||||
<member><xref linkend="sql-refreshmaterializedview"></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
|
||||
</refentry> |
@ -0,0 +1,113 @@ |
||||
<!-- |
||||
doc/src/sgml/ref/refresh_materialized_view.sgml |
||||
PostgreSQL documentation |
||||
--> |
||||
|
||||
<refentry id="SQL-REFRESHMATERIALIZEDVIEW"> |
||||
<refmeta> |
||||
<refentrytitle>REFRESH MATERIALIZED VIEW</refentrytitle> |
||||
<manvolnum>7</manvolnum> |
||||
<refmiscinfo>SQL - Language Statements</refmiscinfo> |
||||
</refmeta> |
||||
|
||||
<refnamediv> |
||||
<refname>REFRESH MATERIALIZED VIEW</refname> |
||||
<refpurpose>replace the contents of a materialized view</refpurpose> |
||||
</refnamediv> |
||||
|
||||
<indexterm zone="sql-refreshmaterializedview"> |
||||
<primary>REFRESH MATERIALIZED VIEW</primary> |
||||
</indexterm> |
||||
|
||||
<refsynopsisdiv> |
||||
<synopsis> |
||||
REFRESH MATERIALIZED VIEW <replaceable class="PARAMETER">name</replaceable> |
||||
[ WITH [ NO ] DATA ] |
||||
</synopsis> |
||||
</refsynopsisdiv> |
||||
|
||||
<refsect1> |
||||
<title>Description</title> |
||||
|
||||
<para> |
||||
<command>REFRESH MATERIALIZED VIEW</command> completely replaces the |
||||
contents of a materialized view. The old contents are discarded. If |
||||
<literal>WITH DATA</literal> is specified (or defaults) the backing query |
||||
is executed to provide the new data, and the materialized view is left in a |
||||
scannable state. If <literal>WITH NO DATA</literal> is specified no new |
||||
data is generated and the materialized view is left in an unscannable |
||||
state. |
||||
</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>Notes</title> |
||||
|
||||
<para> |
||||
While the default index for future |
||||
<xref linkend="SQL-CLUSTER"> |
||||
operations is retained, <command>REFRESH MATERIALIZED VIEW</> does not |
||||
order the generated rows based on this property. If you want the data |
||||
to be ordered upon generation, you must use an <literal>ORDER BY</> |
||||
clause in the backing query. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Examples</title> |
||||
|
||||
<para> |
||||
This command will replace the contents of the materialized view called |
||||
<literal>order_summary</literal> using the query from the materialized |
||||
view's definition, and leave it in a scannable state: |
||||
<programlisting> |
||||
REFRESH MATERIALIZED VIEW order_summary; |
||||
</programlisting> |
||||
</para> |
||||
|
||||
<para> |
||||
This command will free storage associated with the materialized view |
||||
<literal>annual_statistics_basis</literal> and leave it in an unscannable |
||||
state: |
||||
<programlisting> |
||||
REFRESH MATERIALIZED VIEW annual_statistics_basis WITH NO DATA; |
||||
</programlisting> |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>Compatibility</title> |
||||
|
||||
<para> |
||||
<command>REFRESH MATERIALIZED VIEW</command> is a |
||||
<productname>PostgreSQL</productname> extension. |
||||
</para> |
||||
</refsect1> |
||||
|
||||
<refsect1> |
||||
<title>See Also</title> |
||||
|
||||
<simplelist type="inline"> |
||||
<member><xref linkend="sql-creatematerializedview"></member> |
||||
<member><xref linkend="sql-altermaterializedview"></member> |
||||
<member><xref linkend="sql-dropmaterializedview"></member> |
||||
</simplelist> |
||||
</refsect1> |
||||
|
||||
</refentry> |
@ -0,0 +1,374 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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/multixact.h" |
||||
#include "access/relscan.h" |
||||
#include "access/xact.h" |
||||
#include "catalog/catalog.h" |
||||
#include "catalog/heap.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 "rewrite/rewriteHandler.h" |
||||
#include "storage/lmgr.h" |
||||
#include "storage/smgr.h" |
||||
#include "tcop/tcopprot.h" |
||||
#include "utils/snapmgr.h" |
||||
|
||||
|
||||
typedef struct |
||||
{ |
||||
DestReceiver pub; /* publicly-known function pointers */ |
||||
Oid transientoid; /* OID of new heap into which to store */ |
||||
/* These fields are filled by transientrel_startup: */ |
||||
Relation transientrel; /* relation to write to */ |
||||
CommandId output_cid; /* cmin to insert in output tuples */ |
||||
int hi_options; /* heap_insert performance options */ |
||||
BulkInsertState bistate; /* bulk insert state */ |
||||
} DR_transientrel; |
||||
|
||||
static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); |
||||
static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self); |
||||
static void transientrel_shutdown(DestReceiver *self); |
||||
static void transientrel_destroy(DestReceiver *self); |
||||
static void refresh_matview_datafill(DestReceiver *dest, Query *query, |
||||
const char *queryString); |
||||
|
||||
/*
|
||||
* SetRelationIsScannable |
||||
* Make the relation appear scannable. |
||||
* |
||||
* NOTE: This is only implemented for materialized views. The heap starts out |
||||
* in a state that doesn't look scannable, and can only transition from there |
||||
* to scannable, unless a new heap is created. |
||||
* |
||||
* NOTE: caller must be holding an appropriate lock on the relation. |
||||
*/ |
||||
void |
||||
SetRelationIsScannable(Relation relation) |
||||
{ |
||||
Page page; |
||||
|
||||
Assert(relation->rd_rel->relkind == RELKIND_MATVIEW); |
||||
Assert(relation->rd_isscannable == false); |
||||
|
||||
RelationOpenSmgr(relation); |
||||
page = (Page) palloc(BLCKSZ); |
||||
PageInit(page, BLCKSZ, 0); |
||||
smgrextend(relation->rd_smgr, MAIN_FORKNUM, 0, (char *) page, true); |
||||
pfree(page); |
||||
|
||||
smgrimmedsync(relation->rd_smgr, MAIN_FORKNUM); |
||||
|
||||
RelationCacheInvalidateEntry(relation->rd_id); |
||||
} |
||||
|
||||
/*
|
||||
* ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command |
||||
* |
||||
* This refreshes the materialized view by creating a new table and swapping |
||||
* the relfilenodes of the new table and the old materialized view, so the OID |
||||
* of the original materialized view is preserved. Thus we do not lose GRANT |
||||
* nor references to this materialized view. |
||||
* |
||||
* If WITH NO DATA was specified, this is effectively like a TRUNCATE; |
||||
* otherwise it is like a TRUNCATE followed by an INSERT using the SELECT |
||||
* statement associated with the materialized view. The statement node's |
||||
* skipData field is used to indicate that the clause was used. |
||||
* |
||||
* 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. |
||||
* |
||||
* The scannable state is changed based on whether the contents reflect the |
||||
* result set of the materialized view's query. |
||||
*/ |
||||
void |
||||
ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, |
||||
ParamListInfo params, char *completionTag) |
||||
{ |
||||
Oid matviewOid; |
||||
Relation matviewRel; |
||||
RewriteRule *rule; |
||||
List *actions; |
||||
Query *dataQuery; |
||||
Oid tableSpace; |
||||
Oid OIDNewHeap; |
||||
DestReceiver *dest; |
||||
|
||||
/*
|
||||
* 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)); |
||||
|
||||
Assert(!matviewRel->rd_rel->relhasoids); |
||||
|
||||
/*
|
||||
* Check that everything is correct for a refresh. Problems at this point |
||||
* are internal errors, so elog is sufficient. |
||||
*/ |
||||
if (matviewRel->rd_rel->relhasrules == false || |
||||
matviewRel->rd_rules->numLocks < 1) |
||||
elog(ERROR, |
||||
"materialized view \"%s\" is missing rewrite information", |
||||
RelationGetRelationName(matviewRel)); |
||||
|
||||
if (matviewRel->rd_rules->numLocks > 1) |
||||
elog(ERROR, |
||||
"materialized view \"%s\" has too many rules", |
||||
RelationGetRelationName(matviewRel)); |
||||
|
||||
rule = matviewRel->rd_rules->rules[0]; |
||||
if (rule->event != CMD_SELECT || !(rule->isInstead)) |
||||
elog(ERROR, |
||||
"the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", |
||||
RelationGetRelationName(matviewRel)); |
||||
|
||||
actions = rule->actions; |
||||
if (list_length(actions) != 1) |
||||
elog(ERROR, |
||||
"the rule for materialized view \"%s\" is not a single action", |
||||
RelationGetRelationName(matviewRel)); |
||||
|
||||
/*
|
||||
* The stored query was rewritten at the time of the MV definition, but |
||||
* has not been scribbled on by the planner. |
||||
*/ |
||||
dataQuery = (Query *) linitial(actions); |
||||
Assert(IsA(dataQuery, Query)); |
||||
|
||||
/*
|
||||
* Check for active uses of the relation in the current transaction, such |
||||
* as open scans. |
||||
* |
||||
* NB: We count on this to protect us against problems with refreshing the |
||||
* data using HEAP_INSERT_FROZEN. |
||||
*/ |
||||
CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); |
||||
|
||||
tableSpace = matviewRel->rd_rel->reltablespace; |
||||
|
||||
heap_close(matviewRel, NoLock); |
||||
|
||||
/* Create the transient table that will receive the regenerated data. */ |
||||
OIDNewHeap = make_new_heap(matviewOid, tableSpace); |
||||
dest = CreateTransientRelDestReceiver(OIDNewHeap); |
||||
|
||||
if (!stmt->skipData) |
||||
refresh_matview_datafill(dest, dataQuery, queryString); |
||||
|
||||
/*
|
||||
* 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, true, RecentXmin, |
||||
ReadNextMultiXactId()); |
||||
|
||||
RelationCacheInvalidateEntry(matviewOid); |
||||
} |
||||
|
||||
/*
|
||||
* refresh_matview_datafill |
||||
*/ |
||||
static void |
||||
refresh_matview_datafill(DestReceiver *dest, Query *query, |
||||
const char *queryString) |
||||
{ |
||||
List *rewritten; |
||||
PlannedStmt *plan; |
||||
QueryDesc *queryDesc; |
||||
List *rtable; |
||||
RangeTblEntry *initial_rte; |
||||
RangeTblEntry *second_rte; |
||||
|
||||
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 REFRESH MATERIALIZED VIEW"); |
||||
query = (Query *) linitial(rewritten); |
||||
|
||||
/* Check for user-requested abort. */ |
||||
CHECK_FOR_INTERRUPTS(); |
||||
|
||||
/*
|
||||
* Kludge here to allow refresh of a materialized view which is invalid |
||||
* (that is, it was created or refreshed WITH NO DATA. We flag the first |
||||
* two RangeTblEntry list elements, which were added to the front of the |
||||
* rewritten Query to keep the rules system happy, with the isResultRel |
||||
* flag to indicate that it is OK if they are flagged as invalid. See |
||||
* UpdateRangeTableOfViewParse() for details. |
||||
* |
||||
* NOTE: The rewrite has switched the frist two RTEs, but they are still |
||||
* in the first two positions. If that behavior changes, the asserts here |
||||
* will fail. |
||||
*/ |
||||
rtable = query->rtable; |
||||
initial_rte = ((RangeTblEntry *) linitial(rtable)); |
||||
Assert(strcmp(initial_rte->alias->aliasname, "new")); |
||||
initial_rte->isResultRel = true; |
||||
second_rte = ((RangeTblEntry *) lsecond(rtable)); |
||||
Assert(strcmp(second_rte->alias->aliasname, "old")); |
||||
second_rte->isResultRel = true; |
||||
|
||||
/* Plan the query which will generate data for the refresh. */ |
||||
plan = pg_plan_query(query, 0, NULL); |
||||
|
||||
/*
|
||||
* 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, EXEC_FLAG_WITHOUT_OIDS); |
||||
|
||||
/* run the plan */ |
||||
ExecutorRun(queryDesc, ForwardScanDirection, 0L); |
||||
|
||||
/* and clean up */ |
||||
ExecutorFinish(queryDesc); |
||||
ExecutorEnd(queryDesc); |
||||
|
||||
FreeQueryDesc(queryDesc); |
||||
|
||||
PopActiveSnapshot(); |
||||
} |
||||
|
||||
DestReceiver * |
||||
CreateTransientRelDestReceiver(Oid transientoid) |
||||
{ |
||||
DR_transientrel *self = (DR_transientrel *) palloc0(sizeof(DR_transientrel)); |
||||
|
||||
self->pub.receiveSlot = transientrel_receive; |
||||
self->pub.rStartup = transientrel_startup; |
||||
self->pub.rShutdown = transientrel_shutdown; |
||||
self->pub.rDestroy = transientrel_destroy; |
||||
self->pub.mydest = DestTransientRel; |
||||
self->transientoid = transientoid; |
||||
|
||||
return (DestReceiver *) self; |
||||
} |
||||
|
||||
/*
|
||||
* transientrel_startup --- executor startup |
||||
*/ |
||||
static void |
||||
transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) |
||||
{ |
||||
DR_transientrel *myState = (DR_transientrel *) self; |
||||
Relation transientrel; |
||||
|
||||
transientrel = heap_open(myState->transientoid, NoLock); |
||||
|
||||
/*
|
||||
* Fill private fields of myState for use by later routines |
||||
*/ |
||||
myState->transientrel = transientrel; |
||||
myState->output_cid = GetCurrentCommandId(true); |
||||
|
||||
/*
|
||||
* We can skip WAL-logging the insertions, unless PITR or streaming |
||||
* replication is in use. We can skip the FSM in any case. |
||||
*/ |
||||
myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN; |
||||
if (!XLogIsNeeded()) |
||||
myState->hi_options |= HEAP_INSERT_SKIP_WAL; |
||||
myState->bistate = GetBulkInsertState(); |
||||
|
||||
SetRelationIsScannable(transientrel); |
||||
|
||||
/* 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); |
||||
|
||||
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); |
||||
} |
@ -0,0 +1,945 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* rewriteDefine.c |
||||
* routines for defining a rewrite rule |
||||
* |
||||
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* |
||||
* IDENTIFICATION |
||||
* src/backend/rewrite/rewriteDefine.c |
||||
* |
||||
*------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/heapam.h" |
||||
#include "access/htup_details.h" |
||||
#include "access/multixact.h" |
||||
#include "access/transam.h" |
||||
#include "access/xact.h" |
||||
#include "catalog/catalog.h" |
||||
#include "catalog/dependency.h" |
||||
#include "catalog/heap.h" |
||||
#include "catalog/indexing.h" |
||||
#include "catalog/namespace.h" |
||||
#include "catalog/objectaccess.h" |
||||
#include "catalog/pg_rewrite.h" |
||||
#include "catalog/storage.h" |
||||
#include "miscadmin.h" |
||||
#include "nodes/nodeFuncs.h" |
||||
#include "parser/parse_utilcmd.h" |
||||
#include "rewrite/rewriteDefine.h" |
||||
#include "rewrite/rewriteManip.h" |
||||
#include "rewrite/rewriteSupport.h" |
||||
#include "utils/acl.h" |
||||
#include "utils/builtins.h" |
||||
#include "utils/inval.h" |
||||
#include "utils/lsyscache.h" |
||||
#include "utils/rel.h" |
||||
#include "utils/syscache.h" |
||||
#include "utils/tqual.h" |
||||
|
||||
|
||||
static void checkRuleResultList(List *targetList, TupleDesc resultDesc, |
||||
bool isSelect); |
||||
static bool setRuleCheckAsUser_walker(Node *node, Oid *context); |
||||
static void setRuleCheckAsUser_Query(Query *qry, Oid userid); |
||||
|
||||
|
||||
/*
|
||||
* InsertRule - |
||||
* 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, |
||||
AttrNumber evslot_index, |
||||
bool evinstead, |
||||
Node *event_qual, |
||||
List *action, |
||||
bool replace) |
||||
{ |
||||
char *evqual = nodeToString(event_qual); |
||||
char *actiontree = nodeToString((Node *) action); |
||||
Datum values[Natts_pg_rewrite]; |
||||
bool nulls[Natts_pg_rewrite]; |
||||
bool replaces[Natts_pg_rewrite]; |
||||
NameData rname; |
||||
Relation pg_rewrite_desc; |
||||
HeapTuple tup, |
||||
oldtup; |
||||
Oid rewriteObjectId; |
||||
ObjectAddress myself, |
||||
referenced; |
||||
bool is_update = false; |
||||
|
||||
/*
|
||||
* Set up *nulls and *values arrays |
||||
*/ |
||||
MemSet(nulls, false, sizeof(nulls)); |
||||
|
||||
namestrcpy(&rname, rulname); |
||||
values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname); |
||||
values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid); |
||||
values[Anum_pg_rewrite_ev_attr - 1] = Int16GetDatum(evslot_index); |
||||
values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0'); |
||||
values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN); |
||||
values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead); |
||||
values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual); |
||||
values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree); |
||||
|
||||
/*
|
||||
* Ready to store new pg_rewrite tuple |
||||
*/ |
||||
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock); |
||||
|
||||
/*
|
||||
* Check to see if we are replacing an existing tuple |
||||
*/ |
||||
oldtup = SearchSysCache2(RULERELNAME, |
||||
ObjectIdGetDatum(eventrel_oid), |
||||
PointerGetDatum(rulname)); |
||||
|
||||
if (HeapTupleIsValid(oldtup)) |
||||
{ |
||||
if (!replace) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_DUPLICATE_OBJECT), |
||||
errmsg("rule \"%s\" for relation \"%s\" already exists", |
||||
rulname, get_rel_name(eventrel_oid)))); |
||||
|
||||
/*
|
||||
* When replacing, we don't need to replace every attribute |
||||
*/ |
||||
MemSet(replaces, false, sizeof(replaces)); |
||||
replaces[Anum_pg_rewrite_ev_attr - 1] = true; |
||||
replaces[Anum_pg_rewrite_ev_type - 1] = true; |
||||
replaces[Anum_pg_rewrite_is_instead - 1] = true; |
||||
replaces[Anum_pg_rewrite_ev_qual - 1] = true; |
||||
replaces[Anum_pg_rewrite_ev_action - 1] = true; |
||||
|
||||
tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc), |
||||
values, nulls, replaces); |
||||
|
||||
simple_heap_update(pg_rewrite_desc, &tup->t_self, tup); |
||||
|
||||
ReleaseSysCache(oldtup); |
||||
|
||||
rewriteObjectId = HeapTupleGetOid(tup); |
||||
is_update = true; |
||||
} |
||||
else |
||||
{ |
||||
tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls); |
||||
|
||||
rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup); |
||||
} |
||||
|
||||
/* Need to update indexes in either case */ |
||||
CatalogUpdateIndexes(pg_rewrite_desc, tup); |
||||
|
||||
heap_freetuple(tup); |
||||
|
||||
/* If replacing, get rid of old dependencies and make new ones */ |
||||
if (is_update) |
||||
deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false); |
||||
|
||||
/*
|
||||
* Install dependency on rule's relation to ensure it will go away on |
||||
* relation deletion. If the rule is ON SELECT, make the dependency |
||||
* implicit --- this prevents deleting a view's SELECT rule. Other kinds |
||||
* of rules can be AUTO. |
||||
*/ |
||||
myself.classId = RewriteRelationId; |
||||
myself.objectId = rewriteObjectId; |
||||
myself.objectSubId = 0; |
||||
|
||||
referenced.classId = RelationRelationId; |
||||
referenced.objectId = eventrel_oid; |
||||
referenced.objectSubId = 0; |
||||
|
||||
recordDependencyOn(&myself, &referenced, |
||||
(evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO); |
||||
|
||||
/*
|
||||
* Also install dependencies on objects referenced in action and qual. |
||||
*/ |
||||
recordDependencyOnExpr(&myself, (Node *) action, NIL, |
||||
DEPENDENCY_NORMAL); |
||||
|
||||
if (event_qual != NULL) |
||||
{ |
||||
/* Find query containing OLD/NEW rtable entries */ |
||||
Query *qry = (Query *) linitial(action); |
||||
|
||||
qry = getInsertSelectQuery(qry, NULL); |
||||
recordDependencyOnExpr(&myself, event_qual, qry->rtable, |
||||
DEPENDENCY_NORMAL); |
||||
} |
||||
|
||||
/* Post creation hook for new rule */ |
||||
InvokeObjectAccessHook(OAT_POST_CREATE, |
||||
RewriteRelationId, rewriteObjectId, 0, NULL); |
||||
|
||||
heap_close(pg_rewrite_desc, RowExclusiveLock); |
||||
|
||||
return rewriteObjectId; |
||||
} |
||||
|
||||
/*
|
||||
* DefineRule |
||||
* Execute a CREATE RULE command. |
||||
*/ |
||||
Oid |
||||
DefineRule(RuleStmt *stmt, const char *queryString) |
||||
{ |
||||
List *actions; |
||||
Node *whereClause; |
||||
Oid relId; |
||||
|
||||
/* Parse analysis. */ |
||||
transformRuleStmt(stmt, queryString, &actions, &whereClause); |
||||
|
||||
/*
|
||||
* Find and lock the relation. Lock level should match |
||||
* DefineQueryRewrite. |
||||
*/ |
||||
relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false); |
||||
|
||||
/* ... and execute */ |
||||
return DefineQueryRewrite(stmt->rulename, |
||||
relId, |
||||
whereClause, |
||||
stmt->event, |
||||
stmt->instead, |
||||
stmt->replace, |
||||
actions); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* DefineQueryRewrite |
||||
* Create a rule |
||||
* |
||||
* This is essentially the same as DefineRule() except that the rule's |
||||
* action and qual have already been passed through parse analysis. |
||||
*/ |
||||
Oid |
||||
DefineQueryRewrite(char *rulename, |
||||
Oid event_relid, |
||||
Node *event_qual, |
||||
CmdType event_type, |
||||
bool is_instead, |
||||
bool replace, |
||||
List *action) |
||||
{ |
||||
Relation event_relation; |
||||
int event_attno; |
||||
ListCell *l; |
||||
Query *query; |
||||
bool RelisBecomingView = false; |
||||
Oid ruleId = InvalidOid; |
||||
|
||||
/*
|
||||
* If we are installing an ON SELECT rule, we had better grab |
||||
* AccessExclusiveLock to ensure no SELECTs are currently running on the |
||||
* event relation. For other types of rules, it would be sufficient to |
||||
* grab ShareRowExclusiveLock to lock out insert/update/delete actions and |
||||
* to ensure that we lock out current CREATE RULE statements; but because |
||||
* of race conditions in access to catalog entries, we can't do that yet. |
||||
* |
||||
* Note that this lock level should match the one used in DefineRule. |
||||
*/ |
||||
event_relation = heap_open(event_relid, AccessExclusiveLock); |
||||
|
||||
/*
|
||||
* 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_VIEW) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
||||
errmsg("\"%s\" is not a table or view", |
||||
RelationGetRelationName(event_relation)))); |
||||
|
||||
if (!allowSystemTableMods && IsSystemRelation(event_relation)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("permission denied: \"%s\" is a system catalog", |
||||
RelationGetRelationName(event_relation)))); |
||||
|
||||
/*
|
||||
* Check user has permission to apply rules to this relation. |
||||
*/ |
||||
if (!pg_class_ownercheck(event_relid, GetUserId())) |
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, |
||||
RelationGetRelationName(event_relation)); |
||||
|
||||
/*
|
||||
* No rule actions that modify OLD or NEW |
||||
*/ |
||||
foreach(l, action) |
||||
{ |
||||
query = (Query *) lfirst(l); |
||||
if (query->resultRelation == 0) |
||||
continue; |
||||
/* Don't be fooled by INSERT/SELECT */ |
||||
if (query != getInsertSelectQuery(query, NULL)) |
||||
continue; |
||||
if (query->resultRelation == PRS2_OLD_VARNO) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("rule actions on OLD are not implemented"), |
||||
errhint("Use views or triggers instead."))); |
||||
if (query->resultRelation == PRS2_NEW_VARNO) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("rule actions on NEW are not implemented"), |
||||
errhint("Use triggers instead."))); |
||||
} |
||||
|
||||
if (event_type == CMD_SELECT) |
||||
{ |
||||
/*
|
||||
* Rules ON SELECT are restricted to view definitions |
||||
* |
||||
* So there cannot be INSTEAD NOTHING, ... |
||||
*/ |
||||
if (list_length(action) == 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("INSTEAD NOTHING rules on SELECT are not implemented"), |
||||
errhint("Use views instead."))); |
||||
|
||||
/*
|
||||
* ... there cannot be multiple actions, ... |
||||
*/ |
||||
if (list_length(action) > 1) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("multiple actions for rules on SELECT are not implemented"))); |
||||
|
||||
/*
|
||||
* ... the one action must be a SELECT, ... |
||||
*/ |
||||
query = (Query *) linitial(action); |
||||
if (!is_instead || |
||||
query->commandType != CMD_SELECT || |
||||
query->utilityStmt != NULL) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("rules on SELECT must have action INSTEAD SELECT"))); |
||||
|
||||
/*
|
||||
* ... it cannot contain data-modifying WITH ... |
||||
*/ |
||||
if (query->hasModifyingCTE) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("rules on SELECT must not contain data-modifying statements in WITH"))); |
||||
|
||||
/*
|
||||
* ... there can be no rule qual, ... |
||||
*/ |
||||
if (event_qual != NULL) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("event qualifications are not implemented for rules on SELECT"))); |
||||
|
||||
/*
|
||||
* ... the targetlist of the SELECT action must exactly match the |
||||
* event relation, ... |
||||
*/ |
||||
checkRuleResultList(query->targetList, |
||||
RelationGetDescr(event_relation), |
||||
true); |
||||
|
||||
/*
|
||||
* ... there must not be another ON SELECT rule already ... |
||||
*/ |
||||
if (!replace && event_relation->rd_rules != NULL) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < event_relation->rd_rules->numLocks; i++) |
||||
{ |
||||
RewriteRule *rule; |
||||
|
||||
rule = event_relation->rd_rules->rules[i]; |
||||
if (rule->event == CMD_SELECT) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
||||
errmsg("\"%s\" is already a view", |
||||
RelationGetRelationName(event_relation)))); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* ... and finally the rule must be named _RETURN. |
||||
*/ |
||||
if (strcmp(rulename, ViewSelectRuleName) != 0) |
||||
{ |
||||
/*
|
||||
* In versions before 7.3, the expected name was _RETviewname. For |
||||
* backwards compatibility with old pg_dump output, accept that |
||||
* and silently change it to _RETURN. Since this is just a quick |
||||
* backwards-compatibility hack, limit the number of characters |
||||
* checked to a few less than NAMEDATALEN; this saves having to |
||||
* worry about where a multibyte character might have gotten |
||||
* truncated. |
||||
*/ |
||||
if (strncmp(rulename, "_RET", 4) != 0 || |
||||
strncmp(rulename + 4, RelationGetRelationName(event_relation), |
||||
NAMEDATALEN - 4 - 4) != 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
errmsg("view rule for \"%s\" must be named \"%s\"", |
||||
RelationGetRelationName(event_relation), |
||||
ViewSelectRuleName))); |
||||
rulename = pstrdup(ViewSelectRuleName); |
||||
} |
||||
|
||||
/*
|
||||
* Are we converting a relation to a view? |
||||
* |
||||
* If so, check that the relation is empty because the storage for the |
||||
* relation is going to be deleted. Also insist that the rel not have |
||||
* any triggers, indexes, or child tables. (Note: these tests are too |
||||
* strict, because they will reject relations that once had such but |
||||
* don't anymore. But we don't really care, because this whole |
||||
* business of converting relations to views is just a kluge to allow |
||||
* dump/reload of views that participate in circular dependencies.) |
||||
*/ |
||||
if (event_relation->rd_rel->relkind != RELKIND_VIEW) |
||||
{ |
||||
HeapScanDesc scanDesc; |
||||
|
||||
scanDesc = heap_beginscan(event_relation, SnapshotNow, 0, NULL); |
||||
if (heap_getnext(scanDesc, ForwardScanDirection) != NULL) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
||||
errmsg("could not convert table \"%s\" to a view because it is not empty", |
||||
RelationGetRelationName(event_relation)))); |
||||
heap_endscan(scanDesc); |
||||
|
||||
if (event_relation->rd_rel->relhastriggers) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
||||
errmsg("could not convert table \"%s\" to a view because it has triggers", |
||||
RelationGetRelationName(event_relation)), |
||||
errhint("In particular, the table cannot be involved in any foreign key relationships."))); |
||||
|
||||
if (event_relation->rd_rel->relhasindex) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
||||
errmsg("could not convert table \"%s\" to a view because it has indexes", |
||||
RelationGetRelationName(event_relation)))); |
||||
|
||||
if (event_relation->rd_rel->relhassubclass) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
||||
errmsg("could not convert table \"%s\" to a view because it has child tables", |
||||
RelationGetRelationName(event_relation)))); |
||||
|
||||
RelisBecomingView = true; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
/*
|
||||
* For non-SELECT rules, a RETURNING list can appear in at most one of |
||||
* the actions ... and there can't be any RETURNING list at all in a |
||||
* conditional or non-INSTEAD rule. (Actually, there can be at most |
||||
* one RETURNING list across all rules on the same event, but it seems |
||||
* best to enforce that at rule expansion time.) If there is a |
||||
* RETURNING list, it must match the event relation. |
||||
*/ |
||||
bool haveReturning = false; |
||||
|
||||
foreach(l, action) |
||||
{ |
||||
query = (Query *) lfirst(l); |
||||
|
||||
if (!query->returningList) |
||||
continue; |
||||
if (haveReturning) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("cannot have multiple RETURNING lists in a rule"))); |
||||
haveReturning = true; |
||||
if (event_qual != NULL) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("RETURNING lists are not supported in conditional rules"))); |
||||
if (!is_instead) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("RETURNING lists are not supported in non-INSTEAD rules"))); |
||||
checkRuleResultList(query->returningList, |
||||
RelationGetDescr(event_relation), |
||||
false); |
||||
} |
||||
} |
||||
|
||||
/*
|
||||
* This rule is allowed - prepare to install it. |
||||
*/ |
||||
event_attno = -1; |
||||
|
||||
/* discard rule if it's null action and not INSTEAD; it's a no-op */ |
||||
if (action != NIL || is_instead) |
||||
{ |
||||
ruleId = InsertRule(rulename, |
||||
event_type, |
||||
event_relid, |
||||
event_attno, |
||||
is_instead, |
||||
event_qual, |
||||
action, |
||||
replace); |
||||
|
||||
/*
|
||||
* Set pg_class 'relhasrules' field TRUE for event relation. |
||||
* |
||||
* Important side effect: an SI notice is broadcast to force all |
||||
* backends (including me!) to update relcache entries with the new |
||||
* rule. |
||||
*/ |
||||
SetRelationRuleStatus(event_relid, true); |
||||
} |
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* If the relation is becoming a view: |
||||
* - delete the associated storage files |
||||
* - get rid of any system attributes in pg_attribute; a view shouldn't |
||||
* have any of those |
||||
* - remove the toast table; there is no need for it anymore, and its |
||||
* presence would make vacuum slightly more complicated |
||||
* - set relkind to RELKIND_VIEW, and adjust other pg_class fields |
||||
* to be appropriate for a view |
||||
* |
||||
* NB: we had better have AccessExclusiveLock to do this ... |
||||
* --------------------------------------------------------------------- |
||||
*/ |
||||
if (RelisBecomingView) |
||||
{ |
||||
Relation relationRelation; |
||||
Oid toastrelid; |
||||
HeapTuple classTup; |
||||
Form_pg_class classForm; |
||||
|
||||
relationRelation = heap_open(RelationRelationId, RowExclusiveLock); |
||||
toastrelid = event_relation->rd_rel->reltoastrelid; |
||||
|
||||
/* drop storage while table still looks like a table */ |
||||
RelationDropStorage(event_relation); |
||||
DeleteSystemAttributeTuples(event_relid); |
||||
|
||||
/*
|
||||
* Drop the toast table if any. (This won't take care of updating |
||||
* the toast fields in the relation's own pg_class entry; we handle |
||||
* that below.) |
||||
*/ |
||||
if (OidIsValid(toastrelid)) |
||||
{ |
||||
ObjectAddress toastobject; |
||||
|
||||
/*
|
||||
* Delete the dependency of the toast relation on the main |
||||
* relation so we can drop the former without dropping the latter. |
||||
*/ |
||||
deleteDependencyRecordsFor(RelationRelationId, toastrelid, |
||||
false); |
||||
|
||||
/* Make deletion of dependency record visible */ |
||||
CommandCounterIncrement(); |
||||
|
||||
/* Now drop toast table, including its index */ |
||||
toastobject.classId = RelationRelationId; |
||||
toastobject.objectId = toastrelid; |
||||
toastobject.objectSubId = 0; |
||||
performDeletion(&toastobject, DROP_RESTRICT, |
||||
PERFORM_DELETION_INTERNAL); |
||||
} |
||||
|
||||
/*
|
||||
* SetRelationRuleStatus may have updated the pg_class row, so we must |
||||
* advance the command counter before trying to update it again. |
||||
*/ |
||||
CommandCounterIncrement(); |
||||
|
||||
/*
|
||||
* Fix pg_class entry to look like a normal view's, including setting |
||||
* the correct relkind and removal of reltoastrelid/reltoastidxid of |
||||
* the toast table we potentially removed above. |
||||
*/ |
||||
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid)); |
||||
if (!HeapTupleIsValid(classTup)) |
||||
elog(ERROR, "cache lookup failed for relation %u", event_relid); |
||||
classForm = (Form_pg_class) GETSTRUCT(classTup); |
||||
|
||||
classForm->reltablespace = InvalidOid; |
||||
classForm->relpages = 0; |
||||
classForm->reltuples = 0; |
||||
classForm->relallvisible = 0; |
||||
classForm->reltoastrelid = InvalidOid; |
||||
classForm->reltoastidxid = InvalidOid; |
||||
classForm->relhasindex = false; |
||||
classForm->relkind = RELKIND_VIEW; |
||||
classForm->relhasoids = false; |
||||
classForm->relhaspkey = false; |
||||
classForm->relfrozenxid = InvalidTransactionId; |
||||
classForm->relminmxid = InvalidMultiXactId; |
||||
|
||||
simple_heap_update(relationRelation, &classTup->t_self, classTup); |
||||
CatalogUpdateIndexes(relationRelation, classTup); |
||||
|
||||
heap_freetuple(classTup); |
||||
heap_close(relationRelation, RowExclusiveLock); |
||||
} |
||||
|
||||
/* Close rel, but keep lock till commit... */ |
||||
heap_close(event_relation, NoLock); |
||||
|
||||
return ruleId; |
||||
} |
||||
|
||||
/*
|
||||
* checkRuleResultList |
||||
* Verify that targetList produces output compatible with a tupledesc |
||||
* |
||||
* The targetList might be either a SELECT targetlist, or a RETURNING list; |
||||
* isSelect tells which. (This is mostly used for choosing error messages, |
||||
* but also we don't enforce column name matching for RETURNING.) |
||||
*/ |
||||
static void |
||||
checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect) |
||||
{ |
||||
ListCell *tllist; |
||||
int i; |
||||
|
||||
i = 0; |
||||
foreach(tllist, targetList) |
||||
{ |
||||
TargetEntry *tle = (TargetEntry *) lfirst(tllist); |
||||
int32 tletypmod; |
||||
Form_pg_attribute attr; |
||||
char *attname; |
||||
|
||||
/* resjunk entries may be ignored */ |
||||
if (tle->resjunk) |
||||
continue; |
||||
i++; |
||||
if (i > resultDesc->natts) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
isSelect ? |
||||
errmsg("SELECT rule's target list has too many entries") : |
||||
errmsg("RETURNING list has too many entries"))); |
||||
|
||||
attr = resultDesc->attrs[i - 1]; |
||||
attname = NameStr(attr->attname); |
||||
|
||||
/*
|
||||
* Disallow dropped columns in the relation. This won't happen in the |
||||
* cases we actually care about (namely creating a view via CREATE |
||||
* TABLE then CREATE RULE, or adding a RETURNING rule to a view). |
||||
* Trying to cope with it is much more trouble than it's worth, |
||||
* because we'd have to modify the rule to insert dummy NULLs at the |
||||
* right positions. |
||||
*/ |
||||
if (attr->attisdropped) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
||||
errmsg("cannot convert relation containing dropped columns to view"))); |
||||
|
||||
if (isSelect && strcmp(tle->resname, attname) != 0) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
errmsg("SELECT rule's target entry %d has different column name from \"%s\"", i, attname))); |
||||
|
||||
if (attr->atttypid != exprType((Node *) tle->expr)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
isSelect ? |
||||
errmsg("SELECT rule's target entry %d has different type from column \"%s\"", |
||||
i, attname) : |
||||
errmsg("RETURNING list's entry %d has different type from column \"%s\"", |
||||
i, attname))); |
||||
|
||||
/*
|
||||
* Allow typmods to be different only if one of them is -1, ie, |
||||
* "unspecified". This is necessary for cases like "numeric", where |
||||
* the table will have a filled-in default length but the select |
||||
* rule's expression will probably have typmod = -1. |
||||
*/ |
||||
tletypmod = exprTypmod((Node *) tle->expr); |
||||
if (attr->atttypmod != tletypmod && |
||||
attr->atttypmod != -1 && tletypmod != -1) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
isSelect ? |
||||
errmsg("SELECT rule's target entry %d has different size from column \"%s\"", |
||||
i, attname) : |
||||
errmsg("RETURNING list's entry %d has different size from column \"%s\"", |
||||
i, attname))); |
||||
} |
||||
|
||||
if (i != resultDesc->natts) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
isSelect ? |
||||
errmsg("SELECT rule's target list has too few entries") : |
||||
errmsg("RETURNING list has too few entries"))); |
||||
} |
||||
|
||||
/*
|
||||
* setRuleCheckAsUser |
||||
* Recursively scan a query or expression tree and set the checkAsUser |
||||
* field to the given userid in all rtable entries. |
||||
* |
||||
* Note: for a view (ON SELECT rule), the checkAsUser field of the OLD |
||||
* RTE entry will be overridden when the view rule is expanded, and the |
||||
* checkAsUser field of the NEW entry is irrelevant because that entry's |
||||
* requiredPerms bits will always be zero. However, for other types of rules |
||||
* it's important to set these fields to match the rule owner. So we just set |
||||
* them always. |
||||
*/ |
||||
void |
||||
setRuleCheckAsUser(Node *node, Oid userid) |
||||
{ |
||||
(void) setRuleCheckAsUser_walker(node, &userid); |
||||
} |
||||
|
||||
static bool |
||||
setRuleCheckAsUser_walker(Node *node, Oid *context) |
||||
{ |
||||
if (node == NULL) |
||||
return false; |
||||
if (IsA(node, Query)) |
||||
{ |
||||
setRuleCheckAsUser_Query((Query *) node, *context); |
||||
return false; |
||||
} |
||||
return expression_tree_walker(node, setRuleCheckAsUser_walker, |
||||
(void *) context); |
||||
} |
||||
|
||||
static void |
||||
setRuleCheckAsUser_Query(Query *qry, Oid userid) |
||||
{ |
||||
ListCell *l; |
||||
|
||||
/* Set all the RTEs in this query node */ |
||||
foreach(l, qry->rtable) |
||||
{ |
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); |
||||
|
||||
if (rte->rtekind == RTE_SUBQUERY) |
||||
{ |
||||
/* Recurse into subquery in FROM */ |
||||
setRuleCheckAsUser_Query(rte->subquery, userid); |
||||
} |
||||
else |
||||
rte->checkAsUser = userid; |
||||
} |
||||
|
||||
/* Recurse into subquery-in-WITH */ |
||||
foreach(l, qry->cteList) |
||||
{ |
||||
CommonTableExpr *cte = (CommonTableExpr *) lfirst(l); |
||||
|
||||
setRuleCheckAsUser_Query((Query *) cte->ctequery, userid); |
||||
} |
||||
|
||||
/* If there are sublinks, search for them and process their RTEs */ |
||||
if (qry->hasSubLinks) |
||||
query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid, |
||||
QTW_IGNORE_RC_SUBQUERIES); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Change the firing semantics of an existing rule. |
||||
*/ |
||||
void |
||||
EnableDisableRule(Relation rel, const char *rulename, |
||||
char fires_when) |
||||
{ |
||||
Relation pg_rewrite_desc; |
||||
Oid owningRel = RelationGetRelid(rel); |
||||
Oid eventRelationOid; |
||||
HeapTuple ruletup; |
||||
bool changed = false; |
||||
|
||||
/*
|
||||
* Find the rule tuple to change. |
||||
*/ |
||||
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock); |
||||
ruletup = SearchSysCacheCopy2(RULERELNAME, |
||||
ObjectIdGetDatum(owningRel), |
||||
PointerGetDatum(rulename)); |
||||
if (!HeapTupleIsValid(ruletup)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_UNDEFINED_OBJECT), |
||||
errmsg("rule \"%s\" for relation \"%s\" does not exist", |
||||
rulename, get_rel_name(owningRel)))); |
||||
|
||||
/*
|
||||
* Verify that the user has appropriate permissions. |
||||
*/ |
||||
eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class; |
||||
Assert(eventRelationOid == owningRel); |
||||
if (!pg_class_ownercheck(eventRelationOid, GetUserId())) |
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, |
||||
get_rel_name(eventRelationOid)); |
||||
|
||||
/*
|
||||
* Change ev_enabled if it is different from the desired new state. |
||||
*/ |
||||
if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) != |
||||
fires_when) |
||||
{ |
||||
((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled = |
||||
CharGetDatum(fires_when); |
||||
simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup); |
||||
|
||||
/* keep system catalog indexes current */ |
||||
CatalogUpdateIndexes(pg_rewrite_desc, ruletup); |
||||
|
||||
changed = true; |
||||
} |
||||
|
||||
heap_freetuple(ruletup); |
||||
heap_close(pg_rewrite_desc, RowExclusiveLock); |
||||
|
||||
/*
|
||||
* If we changed anything, broadcast a SI inval message to force each |
||||
* backend (including our own!) to rebuild relation's relcache entry. |
||||
* Otherwise they will fail to apply the change promptly. |
||||
*/ |
||||
if (changed) |
||||
CacheInvalidateRelcache(rel); |
||||
} |
||||
|
||||
|
||||
/*
|
||||
* Perform permissions and integrity checks before acquiring a relation lock. |
||||
*/ |
||||
static void |
||||
RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid, |
||||
void *arg) |
||||
{ |
||||
HeapTuple tuple; |
||||
Form_pg_class form; |
||||
|
||||
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
||||
if (!HeapTupleIsValid(tuple)) |
||||
return; /* concurrently dropped */ |
||||
form = (Form_pg_class) GETSTRUCT(tuple); |
||||
|
||||
/* only tables and views can have rules */ |
||||
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE), |
||||
errmsg("\"%s\" is not a table or view", rv->relname))); |
||||
|
||||
if (!allowSystemTableMods && IsSystemClass(form)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
||||
errmsg("permission denied: \"%s\" is a system catalog", |
||||
rv->relname))); |
||||
|
||||
/* you must own the table to rename one of its rules */ |
||||
if (!pg_class_ownercheck(relid, GetUserId())) |
||||
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname); |
||||
|
||||
ReleaseSysCache(tuple); |
||||
} |
||||
|
||||
/*
|
||||
* Rename an existing rewrite rule. |
||||
*/ |
||||
Oid |
||||
RenameRewriteRule(RangeVar *relation, const char *oldName, |
||||
const char *newName) |
||||
{ |
||||
Oid relid; |
||||
Relation targetrel; |
||||
Relation pg_rewrite_desc; |
||||
HeapTuple ruletup; |
||||
Form_pg_rewrite ruleform; |
||||
Oid ruleOid; |
||||
|
||||
/*
|
||||
* Look up name, check permissions, and acquire lock (which we will NOT |
||||
* release until end of transaction). |
||||
*/ |
||||
relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock, |
||||
false, false, |
||||
RangeVarCallbackForRenameRule, |
||||
NULL); |
||||
|
||||
/* Have lock already, so just need to build relcache entry. */ |
||||
targetrel = relation_open(relid, NoLock); |
||||
|
||||
/* Prepare to modify pg_rewrite */ |
||||
pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock); |
||||
|
||||
/* Fetch the rule's entry (it had better exist) */ |
||||
ruletup = SearchSysCacheCopy2(RULERELNAME, |
||||
ObjectIdGetDatum(relid), |
||||
PointerGetDatum(oldName)); |
||||
if (!HeapTupleIsValid(ruletup)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_UNDEFINED_OBJECT), |
||||
errmsg("rule \"%s\" for relation \"%s\" does not exist", |
||||
oldName, RelationGetRelationName(targetrel)))); |
||||
ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup); |
||||
ruleOid = HeapTupleGetOid(ruletup); |
||||
|
||||
/* rule with the new name should not already exist */ |
||||
if (IsDefinedRewriteRule(relid, newName)) |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_DUPLICATE_OBJECT), |
||||
errmsg("rule \"%s\" for relation \"%s\" already exists", |
||||
newName, RelationGetRelationName(targetrel)))); |
||||
|
||||
/*
|
||||
* We disallow renaming ON SELECT rules, because they should always be |
||||
* named "_RETURN". |
||||
*/ |
||||
if (ruleform->ev_type == CMD_SELECT + '0') |
||||
ereport(ERROR, |
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
||||
errmsg("renaming an ON SELECT rule is not allowed"))); |
||||
|
||||
/* OK, do the update */ |
||||
namestrcpy(&(ruleform->rulename), newName); |
||||
|
||||
simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup); |
||||
|
||||
/* keep system catalog indexes current */ |
||||
CatalogUpdateIndexes(pg_rewrite_desc, ruletup); |
||||
|
||||
heap_freetuple(ruletup); |
||||
heap_close(pg_rewrite_desc, RowExclusiveLock); |
||||
|
||||
/*
|
||||
* Invalidate relation's relcache entry so that other backends (and this |
||||
* one too!) are sent SI message to make them rebuild relcache entries. |
||||
* (Ideally this should happen automatically...) |
||||
*/ |
||||
CacheInvalidateRelcache(targetrel); |
||||
|
||||
/*
|
||||
* Close rel, but keep exclusive lock! |
||||
*/ |
||||
relation_close(targetrel, NoLock); |
||||
|
||||
return ruleOid; |
||||
} |
@ -0,0 +1,28 @@ |
||||
/*-------------------------------------------------------------------------
|
||||
* |
||||
* 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" |
||||
#include "utils/relcache.h" |
||||
|
||||
extern void SetRelationIsScannable(Relation relation); |
||||
|
||||
extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, |
||||
ParamListInfo params, char *completionTag); |
||||
|
||||
extern DestReceiver *CreateTransientRelDestReceiver(Oid oid); |
||||
|
||||
#endif /* MATVIEW_H */ |
@ -0,0 +1,406 @@ |
||||
-- create a table to use as a basis for views and materialized views in various combinations |
||||
CREATE TABLE t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); |
||||
INSERT INTO t VALUES |
||||
(1, 'x', 2), |
||||
(2, 'x', 3), |
||||
(3, 'y', 5), |
||||
(4, 'y', 7), |
||||
(5, 'z', 11); |
||||
-- we want a view based on the table, too, since views present additional challenges |
||||
CREATE VIEW tv AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type; |
||||
SELECT * FROM tv; |
||||
type | totamt |
||||
------+-------- |
||||
y | 12 |
||||
z | 11 |
||||
x | 5 |
||||
(3 rows) |
||||
|
||||
-- create a materialized view with no data, and confirm correct behavior |
||||
EXPLAIN (costs off) |
||||
CREATE MATERIALIZED VIEW tm AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA; |
||||
QUERY PLAN |
||||
--------------------- |
||||
HashAggregate |
||||
-> Seq Scan on t |
||||
(2 rows) |
||||
|
||||
CREATE MATERIALIZED VIEW tm AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA; |
||||
SELECT pg_relation_is_scannable('tm'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
f |
||||
(1 row) |
||||
|
||||
SELECT * FROM tm; |
||||
ERROR: materialized view "tm" has not been populated |
||||
HINT: Use the REFRESH MATERIALIZED VIEW command. |
||||
REFRESH MATERIALIZED VIEW tm; |
||||
SELECT pg_relation_is_scannable('tm'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
t |
||||
(1 row) |
||||
|
||||
CREATE UNIQUE INDEX tm_type ON tm (type); |
||||
SELECT * FROM tm; |
||||
type | totamt |
||||
------+-------- |
||||
y | 12 |
||||
z | 11 |
||||
x | 5 |
||||
(3 rows) |
||||
|
||||
-- create various views |
||||
EXPLAIN (costs off) |
||||
CREATE MATERIALIZED VIEW tvm AS SELECT * FROM tv; |
||||
QUERY PLAN |
||||
--------------------- |
||||
HashAggregate |
||||
-> Seq Scan on t |
||||
(2 rows) |
||||
|
||||
CREATE MATERIALIZED VIEW tvm AS SELECT * FROM tv; |
||||
SELECT * FROM tvm; |
||||
type | totamt |
||||
------+-------- |
||||
y | 12 |
||||
z | 11 |
||||
x | 5 |
||||
(3 rows) |
||||
|
||||
CREATE MATERIALIZED VIEW tmm AS SELECT sum(totamt) AS grandtot FROM tm; |
||||
CREATE MATERIALIZED VIEW tvmm AS SELECT sum(totamt) AS grandtot FROM tvm; |
||||
CREATE VIEW tvv AS SELECT sum(totamt) AS grandtot FROM tv; |
||||
EXPLAIN (costs off) |
||||
CREATE MATERIALIZED VIEW tvvm AS SELECT * FROM tvv; |
||||
QUERY PLAN |
||||
--------------------------- |
||||
Aggregate |
||||
-> HashAggregate |
||||
-> Seq Scan on t |
||||
(3 rows) |
||||
|
||||
CREATE MATERIALIZED VIEW tvvm AS SELECT * FROM tvv; |
||||
CREATE VIEW tvvmv AS SELECT * FROM tvvm; |
||||
CREATE MATERIALIZED VIEW bb AS SELECT * FROM tvvmv; |
||||
CREATE INDEX aa ON bb (grandtot); |
||||
-- check that plans seem reasonable |
||||
\d+ tvm |
||||
Materialized view "public.tvm" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
--------+---------+-----------+----------+--------------+------------- |
||||
type | text | | extended | | |
||||
totamt | numeric | | main | | |
||||
View definition: |
||||
SELECT tv.type, |
||||
tv.totamt |
||||
FROM tv; |
||||
|
||||
\d+ tvm |
||||
Materialized view "public.tvm" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
--------+---------+-----------+----------+--------------+------------- |
||||
type | text | | extended | | |
||||
totamt | numeric | | main | | |
||||
View definition: |
||||
SELECT tv.type, |
||||
tv.totamt |
||||
FROM tv; |
||||
|
||||
\d+ tvvm |
||||
Materialized view "public.tvvm" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
----------+---------+-----------+---------+--------------+------------- |
||||
grandtot | numeric | | main | | |
||||
View definition: |
||||
SELECT tvv.grandtot |
||||
FROM tvv; |
||||
|
||||
\d+ bb |
||||
Materialized view "public.bb" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
----------+---------+-----------+---------+--------------+------------- |
||||
grandtot | numeric | | main | | |
||||
Indexes: |
||||
"aa" btree (grandtot) |
||||
View definition: |
||||
SELECT tvvmv.grandtot |
||||
FROM tvvmv; |
||||
|
||||
-- test schema behavior |
||||
CREATE SCHEMA mvschema; |
||||
ALTER MATERIALIZED VIEW tvm SET SCHEMA mvschema; |
||||
\d+ tvm |
||||
\d+ tvmm |
||||
Materialized view "public.tvmm" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
----------+---------+-----------+---------+--------------+------------- |
||||
grandtot | numeric | | main | | |
||||
View definition: |
||||
SELECT sum(tvm.totamt) AS grandtot |
||||
FROM mvschema.tvm; |
||||
|
||||
SET search_path = mvschema, public; |
||||
\d+ tvm |
||||
Materialized view "mvschema.tvm" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
--------+---------+-----------+----------+--------------+------------- |
||||
type | text | | extended | | |
||||
totamt | numeric | | main | | |
||||
View definition: |
||||
SELECT tv.type, |
||||
tv.totamt |
||||
FROM tv; |
||||
|
||||
-- modify the underlying table data |
||||
INSERT INTO t VALUES (6, 'z', 13); |
||||
-- confirm pre- and post-refresh contents of fairly simple materialized views |
||||
SELECT * FROM tm ORDER BY type; |
||||
type | totamt |
||||
------+-------- |
||||
x | 5 |
||||
y | 12 |
||||
z | 11 |
||||
(3 rows) |
||||
|
||||
SELECT * FROM tvm ORDER BY type; |
||||
type | totamt |
||||
------+-------- |
||||
x | 5 |
||||
y | 12 |
||||
z | 11 |
||||
(3 rows) |
||||
|
||||
REFRESH MATERIALIZED VIEW tm; |
||||
REFRESH MATERIALIZED VIEW tvm; |
||||
SELECT * FROM tm ORDER BY type; |
||||
type | totamt |
||||
------+-------- |
||||
x | 5 |
||||
y | 12 |
||||
z | 24 |
||||
(3 rows) |
||||
|
||||
SELECT * FROM tvm ORDER BY type; |
||||
type | totamt |
||||
------+-------- |
||||
x | 5 |
||||
y | 12 |
||||
z | 24 |
||||
(3 rows) |
||||
|
||||
RESET search_path; |
||||
-- confirm pre- and post-refresh contents of nested materialized views |
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tmm; |
||||
QUERY PLAN |
||||
----------------- |
||||
Seq Scan on tmm |
||||
(1 row) |
||||
|
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tvmm; |
||||
QUERY PLAN |
||||
------------------ |
||||
Seq Scan on tvmm |
||||
(1 row) |
||||
|
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tvvm; |
||||
QUERY PLAN |
||||
------------------ |
||||
Seq Scan on tvvm |
||||
(1 row) |
||||
|
||||
SELECT * FROM tmm; |
||||
grandtot |
||||
---------- |
||||
28 |
||||
(1 row) |
||||
|
||||
SELECT * FROM tvmm; |
||||
grandtot |
||||
---------- |
||||
28 |
||||
(1 row) |
||||
|
||||
SELECT * FROM tvvm; |
||||
grandtot |
||||
---------- |
||||
28 |
||||
(1 row) |
||||
|
||||
REFRESH MATERIALIZED VIEW tmm; |
||||
REFRESH MATERIALIZED VIEW tvmm; |
||||
REFRESH MATERIALIZED VIEW tvvm; |
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tmm; |
||||
QUERY PLAN |
||||
----------------- |
||||
Seq Scan on tmm |
||||
(1 row) |
||||
|
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tvmm; |
||||
QUERY PLAN |
||||
------------------ |
||||
Seq Scan on tvmm |
||||
(1 row) |
||||
|
||||
EXPLAIN (costs off) |
||||
SELECT * FROM tvvm; |
||||
QUERY PLAN |
||||
------------------ |
||||
Seq Scan on tvvm |
||||
(1 row) |
||||
|
||||
SELECT * FROM tmm; |
||||
grandtot |
||||
---------- |
||||
41 |
||||
(1 row) |
||||
|
||||
SELECT * FROM tvmm; |
||||
grandtot |
||||
---------- |
||||
41 |
||||
(1 row) |
||||
|
||||
SELECT * FROM tvvm; |
||||
grandtot |
||||
---------- |
||||
41 |
||||
(1 row) |
||||
|
||||
-- test diemv when the mv does not exist |
||||
DROP MATERIALIZED VIEW IF EXISTS tum; |
||||
NOTICE: materialized view "tum" does not exist, skipping |
||||
-- make sure that an unlogged materialized view works (in the absence of a crash) |
||||
CREATE UNLOGGED MATERIALIZED VIEW tum AS SELECT type, sum(amt) AS totamt FROM t GROUP BY type WITH NO DATA; |
||||
SELECT pg_relation_is_scannable('tum'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
f |
||||
(1 row) |
||||
|
||||
SELECT * FROM tum; |
||||
ERROR: materialized view "tum" has not been populated |
||||
HINT: Use the REFRESH MATERIALIZED VIEW command. |
||||
REFRESH MATERIALIZED VIEW tum; |
||||
SELECT pg_relation_is_scannable('tum'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
t |
||||
(1 row) |
||||
|
||||
SELECT * FROM tum; |
||||
type | totamt |
||||
------+-------- |
||||
y | 12 |
||||
z | 24 |
||||
x | 5 |
||||
(3 rows) |
||||
|
||||
REFRESH MATERIALIZED VIEW tum WITH NO DATA; |
||||
SELECT pg_relation_is_scannable('tum'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
f |
||||
(1 row) |
||||
|
||||
SELECT * FROM tum; |
||||
ERROR: materialized view "tum" has not been populated |
||||
HINT: Use the REFRESH MATERIALIZED VIEW command. |
||||
REFRESH MATERIALIZED VIEW tum WITH DATA; |
||||
SELECT pg_relation_is_scannable('tum'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
t |
||||
(1 row) |
||||
|
||||
SELECT * FROM tum; |
||||
type | totamt |
||||
------+-------- |
||||
y | 12 |
||||
z | 24 |
||||
x | 5 |
||||
(3 rows) |
||||
|
||||
-- test diemv when the mv does exist |
||||
DROP MATERIALIZED VIEW IF EXISTS tum; |
||||
-- make sure that dependencies are reported properly when they block the drop |
||||
DROP TABLE t; |
||||
ERROR: cannot drop table t because other objects depend on it |
||||
DETAIL: view tv depends on table t |
||||
view tvv depends on view tv |
||||
materialized view tvvm depends on view tvv |
||||
view tvvmv depends on materialized view tvvm |
||||
materialized view bb depends on view tvvmv |
||||
materialized view mvschema.tvm depends on view tv |
||||
materialized view tvmm depends on materialized view mvschema.tvm |
||||
materialized view tm depends on table t |
||||
materialized view tmm depends on materialized view tm |
||||
HINT: Use DROP ... CASCADE to drop the dependent objects too. |
||||
-- make sure dependencies are dropped and reported |
||||
-- and make sure that transactional behavior is correct on rollback |
||||
-- incidentally leaving some interesting materialized views for pg_dump testing |
||||
BEGIN; |
||||
DROP TABLE t CASCADE; |
||||
NOTICE: drop cascades to 9 other objects |
||||
DETAIL: drop cascades to view tv |
||||
drop cascades to view tvv |
||||
drop cascades to materialized view tvvm |
||||
drop cascades to view tvvmv |
||||
drop cascades to materialized view bb |
||||
drop cascades to materialized view mvschema.tvm |
||||
drop cascades to materialized view tvmm |
||||
drop cascades to materialized view tm |
||||
drop cascades to materialized view tmm |
||||
ROLLBACK; |
||||
-- some additional tests not using base tables |
||||
CREATE VIEW v_test1 AS SELECT 1 moo; |
||||
CREATE VIEW v_test2 AS SELECT moo, 2*moo FROM v_test1 UNION ALL SELECT moo, 3*moo FROM v_test1; |
||||
\d+ v_test2 |
||||
View "public.v_test2" |
||||
Column | Type | Modifiers | Storage | Description |
||||
----------+---------+-----------+---------+------------- |
||||
moo | integer | | plain | |
||||
?column? | integer | | plain | |
||||
View definition: |
||||
SELECT v_test1.moo, |
||||
2 * v_test1.moo |
||||
FROM v_test1 |
||||
UNION ALL |
||||
SELECT v_test1.moo, |
||||
3 * v_test1.moo |
||||
FROM v_test1; |
||||
|
||||
CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM v_test2 UNION ALL SELECT moo, 3*moo FROM v_test2; |
||||
\d+ mv_test2 |
||||
Materialized view "public.mv_test2" |
||||
Column | Type | Modifiers | Storage | Stats target | Description |
||||
----------+---------+-----------+---------+--------------+------------- |
||||
moo | integer | | plain | | |
||||
?column? | integer | | plain | | |
||||
View definition: |
||||
SELECT v_test2.moo, |
||||
2 * v_test2.moo |
||||
FROM v_test2 |
||||
UNION ALL |
||||
SELECT v_test2.moo, |
||||
3 * v_test2.moo |
||||
FROM v_test2; |
||||
|
||||
CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; |
||||
SELECT pg_relation_is_scannable('mv_test3'::regclass); |
||||
pg_relation_is_scannable |
||||
-------------------------- |
||||
t |
||||
(1 row) |
||||
|
||||
DROP VIEW v_test1 CASCADE; |
||||
NOTICE: drop cascades to 3 other objects |
||||
DETAIL: drop cascades to view v_test2 |
||||
drop cascades to materialized view mv_test2 |
||||
drop cascades to materialized view mv_test3 |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue