- GSoC - snapshot materialized view (work-in-progress) patch
Hi all,
here is my patch trying to implement snapshot materialized views. On
http://github.com/pbaros/postgres can be seen too (or downloaded source
code). But it is still in progress. Together with patch I enclosed sql
script that shows what can be done with MV.
Description of patch:
1) can create MV, and is created uninitialized with data
CREATE MATERIALIZED VIEW mvname AS SELECT ...
2) can refresh MV
ALTER MATERIALIZED VIEW mvname REFRESH
3) MV cannot be modified by DML commands (INSERT, UPDATE and DELETE are
not permitted)
4) index can be created and used with MV
5) pg_dump is repaired, in previous patch dump threw error, now dont,
but it is sort of dummy, I want to reach state, where refreshing command
will be posed after all COPY statements (when all data are in tables).
In this patch REFRESH command is right behind CREATE MV command.
6) psql works too, new command \dm[S+] was added to the list
\d[S+] [PATTERN] - lists all db objects like tables, view,
materialized view and sequences
\dm[S+] [PATTERN] - lists all materialized views
7) there are some docs too, but I guess it is not enough, at least my
english will need to correct
8) some ALTER TABLE commands works, ie. RENAME TO, OWNER TO, SET SCHEMA,
SET TABLESPACE
9) MV and columns can be commented
10) also some functions behave as expected, but if you know about some I
did not mention and could fail when used with MV, I appreciate your hints
pg_get_viewdef()
pg_get_ruledef()
pg_relation_filenode()
pg_relation_filepath()
pg_table_size()
In progress:
- regression tests
- behavior of various ALTER commands, ie SET STATISTIC, CLUSTER ON,
ENABLE/DISABLE RULE, etc.
thanks for comments
Pavel Baros
Attachments:
mv_v1.1.patchtext/x-patch; name=mv_v1.1.patchDownload
*** ./doc/src/sgml/catalogs.sgml.orig 2010-07-06 10:43:16.000000000 +0200
--- ./doc/src/sgml/catalogs.sgml 2010-07-08 15:22:14.000000000 +0200
***************
*** 1559,1565 ****
<entry></entry>
<entry>
<literal>r</> = ordinary table, <literal>i</> = index,
! <literal>S</> = sequence, <literal>v</> = view, <literal>c</> =
composite type, <literal>t</> = TOAST
table
</entry>
--- 1559,1565 ----
<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
</entry>
***************
*** 4086,4092 ****
<entry>
Event type that the rule is for: 1 = <command>SELECT</>, 2 =
<command>UPDATE</>, 3 = <command>INSERT</>, 4 =
! <command>DELETE</>
</entry>
</row>
--- 4086,4092 ----
<entry>
Event type that the rule is for: 1 = <command>SELECT</>, 2 =
<command>UPDATE</>, 3 = <command>INSERT</>, 4 =
! <command>DELETE</>, 6 = <command>REFRESH</>
</entry>
</row>
*** ./doc/src/sgml/ref/alter_view.sgml.orig 2010-07-06 10:43:16.000000000 +0200
--- ./doc/src/sgml/ref/alter_view.sgml 2010-07-08 15:22:14.000000000 +0200
***************
*** 23,31 ****
<synopsis>
ALTER VIEW <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
ALTER VIEW <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> DROP DEFAULT
! ALTER VIEW <replaceable class="parameter">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
! ALTER VIEW <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
! ALTER VIEW <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
</synopsis>
</refsynopsisdiv>
--- 23,32 ----
<synopsis>
ALTER VIEW <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
ALTER VIEW <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> DROP DEFAULT
! ALTER [ MATERIALIZED ] VIEW <replaceable class="parameter">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
! ALTER [ MATERIALIZED ] VIEW <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
! ALTER [ MATERIALIZED ] VIEW <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
! ALTER MATERIALIZED VIEW <replaceable class="parameter">name</replaceable> REFRESH
</synopsis>
</refsynopsisdiv>
***************
*** 102,107 ****
--- 103,117 ----
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>REFRESH</literal></term>
+ <listitem>
+ <para>
+ Refresh data in the materialized view by rebuilding definition.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
*** ./doc/src/sgml/ref/comment.sgml.orig 2010-07-06 10:43:16.000000000 +0200
--- ./doc/src/sgml/ref/comment.sgml 2010-07-08 15:22:14.000000000 +0200
***************
*** 49,55 ****
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
! VIEW <replaceable class="PARAMETER">object_name</replaceable>
} IS '<replaceable class="PARAMETER">text</replaceable>'
</synopsis>
</refsynopsisdiv>
--- 49,55 ----
TEXT SEARCH TEMPLATE <replaceable class="PARAMETER">object_name</replaceable> |
TRIGGER <replaceable class="PARAMETER">trigger_name</replaceable> ON <replaceable class="PARAMETER">table_name</replaceable> |
TYPE <replaceable class="PARAMETER">object_name</replaceable> |
! [ MATERIALIZED ] VIEW <replaceable class="PARAMETER">object_name</replaceable>
} IS '<replaceable class="PARAMETER">text</replaceable>'
</synopsis>
</refsynopsisdiv>
***************
*** 268,273 ****
--- 268,274 ----
COMMENT ON TRIGGER my_trigger ON my_table IS 'Used for RI';
COMMENT ON TYPE complex IS 'Complex number data type';
COMMENT ON VIEW my_view IS 'View of departmental costs';
+ COMMENT ON MATERIALIZED VIEW salaries_mv IS 'Materialized View of salaries according to the profession';
</programlisting>
</para>
</refsect1>
*** ./doc/src/sgml/ref/create_view.sgml.orig 2010-07-06 20:32:12.000000000 +0200
--- ./doc/src/sgml/ref/create_view.sgml 2010-07-08 15:22:14.000000000 +0200
***************
*** 21,27 ****
<refsynopsisdiv>
<synopsis>
! CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
AS <replaceable class="PARAMETER">query</replaceable>
</synopsis>
</refsynopsisdiv>
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
! CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ MATERIALIZED ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
AS <replaceable class="PARAMETER">query</replaceable>
</synopsis>
</refsynopsisdiv>
***************
*** 80,85 ****
--- 80,97 ----
</varlistentry>
<varlistentry>
+ <term><literal>MATERIALIZED</></term>
+ <listitem>
+ <para>
+ If specified, view is created materialized. The result of
+ definition is stored in physical table. Materialized view
+ is created empty, so to fill materialized view with data
+ run separate refresh command <xref linkend="sql-alterview">.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
*** ./src/backend/access/common/reloptions.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/access/common/reloptions.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 775,780 ****
--- 775,781 ----
switch (classForm->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
case RELKIND_TOASTVALUE:
case RELKIND_UNCATALOGED:
options = heap_reloptions(classForm->relkind, datum, false);
***************
*** 1172,1177 ****
--- 1173,1179 ----
}
return (bytea *) rdopts;
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* sequences, composite types and views are not supported */
*** ./src/backend/access/heap/heapam.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/access/heap/heapam.c 2010-07-08 20:42:56.000000000 +0200
***************
*** 1877,1883 ****
* Note: below this point, heaptup is the data we actually intend to store
* into the relation; tup is the caller's original untoasted data.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(tup));
--- 1877,1884 ----
* Note: below this point, heaptup is the data we actually intend to store
* into the relation; tup is the caller's original untoasted data.
*/
! if (relation->rd_rel->relkind != RELKIND_RELATION &&
! relation->rd_rel->relkind != RELKIND_MATVIEW)
{
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(tup));
***************
*** 4126,4132 ****
/*
* We're about to remove tuples. In Hot Standby mode, ensure that there's
* no queries running for which the removed tuples are still visible.
! *
* Not all HEAP2_CLEAN records remove tuples with xids, so we only want to
* conflict on the records that cause MVCC failures for user queries. If
* latestRemovedXid is invalid, skip conflict processing.
--- 4127,4133 ----
/*
* We're about to remove tuples. In Hot Standby mode, ensure that there's
* no queries running for which the removed tuples are still visible.
! *
* Not all HEAP2_CLEAN records remove tuples with xids, so we only want to
* conflict on the records that cause MVCC failures for user queries. If
* latestRemovedXid is invalid, skip conflict processing.
*** ./src/backend/catalog/dependency.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/catalog/dependency.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 2731,2736 ****
--- 2731,2740 ----
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);
*** ./src/backend/catalog/heap.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/catalog/heap.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 758,763 ****
--- 758,764 ----
case RELKIND_RELATION:
case RELKIND_INDEX:
case RELKIND_TOASTVALUE:
+ case RELKIND_MATVIEW:
/* The relation is real, but as yet empty */
new_rel_reltup->relpages = 0;
new_rel_reltup->reltuples = 0;
***************
*** 776,782 ****
/* Initialize relfrozenxid */
if (relkind == RELKIND_RELATION ||
! relkind == RELKIND_TOASTVALUE)
{
/*
* Initialize to the minimum XID that could put tuples in the table.
--- 777,784 ----
/* Initialize relfrozenxid */
if (relkind == RELKIND_RELATION ||
! relkind == RELKIND_TOASTVALUE ||
! relkind == RELKIND_MATVIEW)
{
/*
* Initialize to the minimum XID that could put tuples in the table.
***************
*** 1027,1032 ****
--- 1029,1035 ----
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW ||
relkind == RELKIND_COMPOSITE_TYPE))
new_array_oid = AssignTypeArrayOid();
*** ./src/backend/catalog/system_views.sql.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/catalog/system_views.sql 2010-07-08 15:22:14.000000000 +0200
***************
*** 76,82 ****
pg_get_userbyid(C.relowner) AS viewowner,
pg_get_viewdef(C.oid) AS definition
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
! WHERE C.relkind = 'v';
CREATE VIEW pg_tables AS
SELECT
--- 76,82 ----
pg_get_userbyid(C.relowner) AS viewowner,
pg_get_viewdef(C.oid) AS definition
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
! WHERE C.relkind = 'v' OR C.relkind = 'm';
CREATE VIEW pg_tables AS
SELECT
*** ./src/backend/commands/alter.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/alter.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 87,92 ****
--- 87,93 ----
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_INDEX:
case OBJECT_COLUMN:
case OBJECT_TRIGGER:
***************
*** 102,107 ****
--- 103,109 ----
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_INDEX:
{
/*
***************
*** 188,193 ****
--- 190,196 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
CheckRelationOwnership(stmt->relation, true);
AlterTableNamespace(stmt->relation, stmt->newschema,
stmt->objectType);
*** ./src/backend/commands/comment.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/comment.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 107,112 ****
--- 107,113 ----
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
CommentRelation(stmt->objtype, stmt->objname, stmt->comment);
break;
case OBJECT_COLUMN:
***************
*** 580,585 ****
--- 581,593 ----
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;
}
/* Create the comment using the relation's oid */
***************
*** 636,641 ****
--- 644,650 ----
*/
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)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
*** ./src/backend/commands/copy.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/copy.c 2010-07-08 15:22:14.000000000 +0200
***************
*** 1227,1233 ****
if (cstate->rel)
{
! if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
--- 1227,1233 ----
if (cstate->rel)
{
! if (cstate->rel->rd_rel->relkind != RELKIND_RELATION && cstate->rel->rd_rel->relkind != RELKIND_MATVIEW)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
***************
*** 1701,1707 ****
Assert(cstate->rel);
! if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
--- 1701,1707 ----
Assert(cstate->rel);
! if (cstate->rel->rd_rel->relkind != RELKIND_RELATION && cstate->rel->rd_rel->relkind != RELKIND_MATVIEW)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
*** ./src/backend/commands/indexcmds.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/indexcmds.c 2010-07-08 20:48:53.000000000 +0200
***************
*** 181,186 ****
--- 181,187 ----
/* Note: during bootstrap may see uncataloged relation */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_UNCATALOGED)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
*** ./src/backend/commands/tablecmds.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/tablecmds.c 2010-07-08 20:52:26.000000000 +0200
***************
*** 205,210 ****
--- 205,216 ----
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"),
***************
*** 678,683 ****
--- 684,693 ----
relkind = RELKIND_VIEW;
break;
+ case OBJECT_MATVIEW:
+ relkind = RELKIND_MATVIEW;
+ break;
+
default:
elog(ERROR, "unrecognized drop object type: %d",
(int) drop->removeType);
***************
*** 1970,1977 ****
relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type or index",
! RelationGetRelationName(targetrelation))));
/*
* permissions checking. only the owner of a class can change its schema.
--- 1980,1987 ----
relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table, view, composite type or index",
! RelationGetRelationName(targetrelation))));
/*
* permissions checking. only the owner of a class can change its schema.
***************
*** 2358,2363 ****
--- 2368,2381 ----
RelationGetRelationName(rel))));
break;
+ case OBJECT_MATVIEW:
+ if (rel->rd_rel->relkind != RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a materialized view",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmt->relkind);
}
***************
*** 3321,3326 ****
--- 3339,3350 ----
{
if (rel->rd_rel->relkind != RELKIND_RELATION)
{
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Materialized view \"%s\" cannot be altered",
+ RelationGetRelationName(rel))));
+
if (allowView)
{
if (rel->rd_rel->relkind != RELKIND_VIEW)
***************
*** 3359,3364 ****
--- 3383,3389 ----
ATSimplePermissionsRelationOrIndex(Relation rel)
{
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 4111,4116 ****
--- 4136,4142 ----
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
***************
*** 6439,6444 ****
--- 6465,6471 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
/* ok to change owner */
break;
case RELKIND_INDEX:
***************
*** 7741,7746 ****
--- 7768,7781 ----
RelationGetRelationName(rel))));
break;
+ case OBJECT_MATVIEW:
+ if (rel->rd_rel->relkind != RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a materialized view",
+ RelationGetRelationName(rel))));
+ break;
+
default:
elog(ERROR, "unrecognized object type: %d", (int) stmttype);
}
***************
*** 7750,7755 ****
--- 7785,7791 ----
{
case RELKIND_RELATION:
case RELKIND_VIEW:
+ case RELKIND_MATVIEW:
/* ok to change schema */
break;
case RELKIND_SEQUENCE:
***************
*** 7822,7827 ****
--- 7858,7869 ----
AlterConstraintNamespaces(relid, oldNspOid, nspOid, false);
}
+ /* Fix other dependent stuff with materialized view */
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ {
+ AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid);
+ }
+
heap_close(classRel, RowExclusiveLock);
/* close rel, but keep lock until commit */
*** ./src/backend/commands/view.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/commands/view.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 97,103 ****
*---------------------------------------------------------------------
*/
static Oid
! DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
{
Oid viewOid,
namespaceId;
--- 97,103 ----
*---------------------------------------------------------------------
*/
static Oid
! DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace, char relkind)
{
Oid viewOid,
namespaceId;
***************
*** 155,160 ****
--- 155,168 ----
rel = relation_open(viewOid, AccessExclusiveLock);
/*
+ * Check if do not try to replace materialized view.
+ */
+ if (rel->rd_rel->relkind == RELKIND_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CREATE OR REPLACE on materialized view is not supported!"),
+ errhint("Use CREATE MATERIALIZED VIEW ...")));
+ /*
* Make sure it *is* a view, and do permissions checks.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW)
***************
*** 239,245 ****
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
! return DefineRelation(createStmt, RELKIND_VIEW);
}
}
--- 247,253 ----
* existing view, so we don't need more code to complain if "replace"
* is false).
*/
! return DefineRelation(createStmt, relkind);
}
}
***************
*** 299,305 ****
}
static void
! DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
{
/*
* Set up the ON SELECT rule. Since the query has already been through
--- 307,313 ----
}
static void
! DefineViewRules(Oid viewOid, Query *viewParse, bool is_instead, bool replace, bool is_materialized)
{
/*
* Set up the ON SELECT rule. Since the query has already been through
***************
*** 308,315 ****
DefineQueryRewrite(pstrdup(ViewSelectRuleName),
viewOid,
NULL,
! CMD_SELECT,
! true,
replace,
list_make1(viewParse));
--- 316,323 ----
DefineQueryRewrite(pstrdup(ViewSelectRuleName),
viewOid,
NULL,
! is_materialized ? CMD_REFRESH : CMD_SELECT,
! is_materialized ? false : is_instead,
replace,
list_make1(viewParse));
***************
*** 465,471 ****
* aborted.
*/
viewOid = DefineVirtualRelation(view, viewParse->targetList,
! stmt->replace);
/*
* The relation we have just created is not visible to any other commands
--- 473,479 ----
* aborted.
*/
viewOid = DefineVirtualRelation(view, viewParse->targetList,
! stmt->replace, (stmt->ismaterialized ? RELKIND_MATVIEW : RELKIND_VIEW));
/*
* The relation we have just created is not visible to any other commands
***************
*** 483,487 ****
/*
* Now create the rules associated with the view.
*/
! DefineViewRules(viewOid, viewParse, stmt->replace);
}
--- 491,495 ----
/*
* Now create the rules associated with the view.
*/
! DefineViewRules(viewOid, viewParse, true, stmt->replace, stmt->ismaterialized);
}
*** ./src/backend/executor/execMain.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/executor/execMain.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 39,49 ****
--- 39,52 ----
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/toasting.h"
+ #include "catalog/dependency.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
+ #include "commands/cluster.h"
#include "executor/execdebug.h"
#include "executor/instrument.h"
#include "miscadmin.h"
+ #include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
***************
*** 56,61 ****
--- 59,65 ----
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
+ #include "lib/stringinfo.h"
/* Hooks for plugins to get control in ExecutorStart/Run/End() */
***************
*** 868,873 ****
--- 872,878 ----
switch (resultRelationDesc->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
/* OK */
break;
case RELKIND_SEQUENCE:
***************
*** 2073,2078 ****
--- 2078,2085 ----
Oid intoRelationId;
TupleDesc tupdesc;
DR_intorel *myState;
+ Oid tableOid;
+ Oid OIDNewHeap;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Assert(into);
***************
*** 2144,2209 ****
get_tablespace_name(tablespaceId));
}
! /* Parse and validate any reloptions */
! reloptions = transformRelOptions((Datum) 0,
! into->options,
! NULL,
! validnsps,
! true,
! false);
! (void) heap_reloptions(RELKIND_RELATION, reloptions, true);
!
! /* Copy the tupdesc because heap_create_with_catalog modifies it */
! tupdesc = CreateTupleDescCopy(queryDesc->tupDesc);
!
! /* Now we can actually create the new relation */
! intoRelationId = heap_create_with_catalog(intoName,
! namespaceId,
! tablespaceId,
! InvalidOid,
! InvalidOid,
! InvalidOid,
! GetUserId(),
! tupdesc,
! NIL,
! RELKIND_RELATION,
! false,
! false,
! true,
! 0,
! into->onCommit,
! reloptions,
! true,
! allowSystemTableMods);
!
! FreeTupleDesc(tupdesc);
!
! /*
! * Advance command counter so that the newly-created relation's catalog
! * tuples will be visible to heap_open.
! */
! CommandCounterIncrement();
!
! /*
! * If necessary, create a TOAST table for the INTO relation. Note that
! * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
! * the TOAST table will be visible for insertion.
! */
! reloptions = transformRelOptions((Datum) 0,
! into->options,
! "toast",
! validnsps,
! true,
! false);
! (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true);
! AlterTableCreateToastTable(intoRelationId, reloptions);
- /*
- * And open the constructed table for writing.
- */
- intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
/*
* Now replace the query's DestReceiver with one for SELECT INTO
--- 2151,2240 ----
get_tablespace_name(tablespaceId));
}
! /*
! * There are two types of SELECT INTO stmt. One for refreshing data
! * in materialized views. The second one for regular SELECT INTO stmt.
! */
! if (into->isrefresh)
! {
! /* Open the constructed table for copying */
! intoRelationDesc = heap_openrv(into->rel, AccessExclusiveLock);
! tableOid = RelationGetRelid(intoRelationDesc);
! /* Close relcache entry */
! heap_close(intoRelationDesc, NoLock);
!
! /* Create new heap, target of REFRESH command */
! OIDNewHeap = make_new_heap(tableOid, tablespaceId);
!
! /* And open the constructed table for writing */
! intoRelationDesc = heap_open(OIDNewHeap, AccessExclusiveLock);
! }
! else
! {
! /* Parse and validate any reloptions */
! reloptions = transformRelOptions((Datum) 0,
! into->options,
! NULL,
! validnsps,
! true,
! false);
! (void) heap_reloptions(RELKIND_RELATION, reloptions, true);
!
! /* Copy the tupdesc because heap_create_with_catalog modifies it */
! tupdesc = CreateTupleDescCopy(queryDesc->tupDesc);
!
! /* Now we can actually create the new relation */
! intoRelationId = heap_create_with_catalog(intoName,
! namespaceId,
! tablespaceId,
! InvalidOid,
! InvalidOid,
! InvalidOid,
! GetUserId(),
! tupdesc,
! NIL,
! RELKIND_RELATION,
! false,
! false,
! true,
! 0,
! into->onCommit,
! reloptions,
! true,
! allowSystemTableMods);
!
! FreeTupleDesc(tupdesc);
!
! /*
! * Advance command counter so that the newly-created relation's catalog
! * tuples will be visible to heap_open.
! */
! CommandCounterIncrement();
!
! /*
! * If necessary, create a TOAST table for the INTO relation. Note that
! * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
! * the TOAST table will be visible for insertion.
! */
! reloptions = transformRelOptions((Datum) 0,
! into->options,
! "toast",
! validnsps,
! true,
! false);
!
! (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true);
!
! AlterTableCreateToastTable(intoRelationId, reloptions);
!
! /*
! * And open the constructed table for writing.
! */
! intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
! }
/*
* Now replace the query's DestReceiver with one for SELECT INTO
***************
*** 2233,2238 ****
--- 2264,2274 ----
CloseIntoRel(QueryDesc *queryDesc)
{
DR_intorel *myState = (DR_intorel *) queryDesc->dest;
+ IntoClause *into = queryDesc->plannedstmt->intoClause;
+ Relation relation;
+ Oid tableOid;
+ Oid tableSpace;
+ Oid OIDTempHeap;
/* OpenIntoRel might never have gotten called */
if (myState && myState->pub.mydest == DestIntoRel && myState->rel)
***************
*** 2246,2251 ****
--- 2282,2301 ----
/* close rel, but keep lock until commit */
heap_close(myState->rel, NoLock);
+ /* check if it's REFRESH command */
+ if (queryDesc->plannedstmt->intoClause->isrefresh)
+ {
+ relation = heap_openrv(into->rel, AccessExclusiveLock);
+ tableOid = relation->rd_id;
+ tableSpace = myState->rel->rd_rel->reltablespace;
+ OIDTempHeap = RelationGetRelid(myState->rel);
+
+ heap_close(relation, NoLock);
+
+ /* swap old heap with new one */
+ finish_heap_swap(tableOid, OIDTempHeap, false, true, myState->rel->rd_rel->relfrozenxid);
+ }
+
myState->rel = NULL;
}
}
*** ./src/backend/nodes/copyfuncs.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/nodes/copyfuncs.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 930,935 ****
--- 930,936 ----
COPY_NODE_FIELD(options);
COPY_SCALAR_FIELD(onCommit);
COPY_STRING_FIELD(tableSpaceName);
+ COPY_SCALAR_FIELD(isrefresh);
return newnode;
}
***************
*** 2845,2850 ****
--- 2846,2852 ----
COPY_NODE_FIELD(aliases);
COPY_NODE_FIELD(query);
COPY_SCALAR_FIELD(replace);
+ COPY_SCALAR_FIELD(ismaterialized);
return newnode;
}
*** ./src/backend/nodes/equalfuncs.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/nodes/equalfuncs.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 119,124 ****
--- 119,125 ----
COMPARE_NODE_FIELD(options);
COMPARE_SCALAR_FIELD(onCommit);
COMPARE_STRING_FIELD(tableSpaceName);
+ COMPARE_SCALAR_FIELD(isrefresh);
return true;
}
***************
*** 1381,1386 ****
--- 1382,1388 ----
COMPARE_NODE_FIELD(aliases);
COMPARE_NODE_FIELD(query);
COMPARE_SCALAR_FIELD(replace);
+ COMPARE_SCALAR_FIELD(ismaterialized);
return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/nodes/outfuncs.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 816,821 ****
--- 816,822 ----
WRITE_NODE_FIELD(options);
WRITE_ENUM_FIELD(onCommit, OnCommitAction);
WRITE_STRING_FIELD(tableSpaceName);
+ WRITE_BOOL_FIELD(isrefresh);
}
static void
*** ./src/backend/parser/analyze.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/parser/analyze.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 28,33 ****
--- 28,34 ----
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+ #include "nodes/pg_list.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
***************
*** 64,69 ****
--- 65,72 ----
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
+ static SelectStmt *build_refresh_stmt(RangeVar *rv);
+ static bool is_matview_refresh(Node *parseTree);
/*
* parse_analyze
***************
*** 149,154 ****
--- 152,250 ----
return query;
}
+
+ /*
+ * Returns true if ALTER TABLE command is REFRESH
+ * and is the only one command in statement, otherwise return false.
+ *
+ */
+ static bool
+ is_matview_refresh(Node *parseTree)
+ {
+ AlterTableStmt *alter;
+ AlterTableCmd *command;
+ ListCell *l;
+ bool is_refresh = false;
+
+ if (nodeTag(parseTree) == T_AlterTableStmt)
+ {
+ alter = (AlterTableStmt *) parseTree;
+
+ /* check out all types of commands */
+ foreach (l, alter->cmds)
+ {
+ command = (AlterTableCmd *) lfirst(l);
+ if (command->subtype == AT_Refresh)
+ is_refresh = true;
+ }
+
+ /* if is present REFRESH cmd */
+ if (is_refresh)
+ {
+ /* we are expecting just one command, else show error message */
+ if (alter->cmds->length == 1)
+ return true;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("REFRESH cannot be used with other ALTER commands")));
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Returns complete parsed SELECT INTO stmt, isRefresh flag
+ * in IntoClause indicate it is REFRESH command.
+ */
+ static SelectStmt *
+ build_refresh_stmt(RangeVar *rv)
+ {
+ /* SELECT * INTO matview FROM [matview definition] */
+ IntoClause *intoClause = makeNode(IntoClause);
+ SelectStmt *selectStmt = makeNode(SelectStmt);
+ ColumnRef *cref;
+ A_Star *star;
+ ResTarget *target;
+
+ memset(selectStmt, 0, sizeof(SelectStmt));
+ memset(intoClause, 0, sizeof(IntoClause));
+
+ intoClause->type = T_IntoClause;
+ intoClause->rel = copyObject(rv);
+ intoClause->colNames = NIL;
+ intoClause->options = NIL;
+ intoClause->onCommit = ONCOMMIT_NOOP;
+ intoClause->tableSpaceName = '\0';
+ intoClause->isrefresh = true;
+
+ /* make A_Star node */
+ star = (A_Star *) makeNode(A_Star);
+ star->type = T_A_Star;
+
+ /* make ColumnRef node and to the 'fields' add A_Star */
+ cref = (ColumnRef *) makeNode(ColumnRef);
+ cref->type = T_ColumnRef;
+ cref->fields = list_make1(star);
+ cref->location = 7;
+ /* make ResTarget node and to the 'val' field add list of ColumnRefs */
+ target = (ResTarget *) makeNode(ResTarget);
+ target->type = T_ResTarget;
+ target->name = "";
+ target->indirection = NIL;
+ target->location = 7;
+ target->val = (Node *) cref;
+
+ /* finally create SelectStmt, append target list and intoClause */
+ selectStmt->type = T_SelectStmt;
+ selectStmt->fromClause = list_make1(intoClause->rel);
+ selectStmt->targetList = list_make1(target);
+ selectStmt->intoClause = intoClause;
+ selectStmt->op = SETOP_NONE;
+
+ return selectStmt;
+ }
+
/*
* transformStmt -
* transform a Parse tree into a Query tree.
***************
*** 191,196 ****
--- 287,324 ----
/*
* Special cases
*/
+ case T_AlterTableStmt:
+ {
+ AlterTableStmt *alter = (AlterTableStmt *) parseTree;
+ AlterTableCmd *command;
+ SelectStmt *selectStmt;
+
+ /*
+ * Check if it's REFRESH-ing materialized view,
+ * if not, do the same as default.
+ */
+ if (alter->relkind == OBJECT_MATVIEW &&
+ is_matview_refresh(parseTree))
+ {
+ command = (AlterTableCmd *) linitial(alter->cmds);
+ /* build parsed REFRESH (SELECT INTO) stmt manualy */
+ selectStmt = build_refresh_stmt(alter->relation);
+ /* transfrom as common SELECT INTO */
+ result = transformSelectStmt(pstate, selectStmt);
+ }
+ else
+ {
+ /*
+ * other statements don't require any transformation; just return
+ * the original parsetree with a Query node plastered on top.
+ */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) parseTree;
+ }
+ }
+ break;
+
case T_DeclareCursorStmt:
result = transformDeclareCursorStmt(pstate,
(DeclareCursorStmt *) parseTree);
***************
*** 261,266 ****
--- 389,409 ----
result = true;
break;
+ case T_AlterTableStmt:
+ {
+ AlterTableStmt *alter = (AlterTableStmt *) parseTree;
+
+ /*
+ * Check if it's REFRESH-ing materialized view,
+ * if not, do the same as default.
+ */
+ if (alter->relkind == OBJECT_MATVIEW && is_matview_refresh(parseTree))
+ result = true;
+ else
+ result = false;
+ }
+ break;
+
default:
/* other utility statements don't have any real parse analysis */
result = false;
*** ./src/backend/parser/gram.y.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/parser/gram.y 2010-07-08 15:22:15.000000000 +0200
***************
*** 310,316 ****
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
! %type <boolean> TriggerForType OptTemp
%type <oncommit> OnCommitOption
%type <node> for_locking_item
--- 310,316 ----
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
! %type <boolean> TriggerForType OptTemp OptMaterialized
%type <oncommit> OnCommitOption
%type <node> for_locking_item
***************
*** 502,508 ****
LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
LOCATION LOCK_P LOGIN_P
! MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
--- 502,508 ----
LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
LOCATION LOCK_P LOGIN_P
! MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
***************
*** 517,523 ****
QUOTE
! RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
--- 517,523 ----
QUOTE
! RANGE READ REAL REASSIGN RECHECK RECURSIVE REFERENCES REFRESH REINDEX
RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART
RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE
***************
*** 1598,1603 ****
--- 1598,1611 ----
n->relkind = OBJECT_VIEW;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $4;
+ n->cmds = $5;
+ n->relkind = OBJECT_MATVIEW;
+ $$ = (Node *)n;
+ }
;
alter_table_cmds:
***************
*** 1914,1919 ****
--- 1922,1934 ----
n->def = (Node *)$2;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> REFRESH */
+ | REFRESH
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_Refresh;
+ $$ = (Node *)n;
+ }
;
alter_column_default:
***************
*** 4046,4051 ****
--- 4061,4067 ----
drop_type: TABLE { $$ = OBJECT_TABLE; }
| SEQUENCE { $$ = OBJECT_SEQUENCE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| INDEX { $$ = OBJECT_INDEX; }
| TYPE_P { $$ = OBJECT_TYPE; }
| DOMAIN_P { $$ = OBJECT_DOMAIN; }
***************
*** 4102,4108 ****
* The COMMENT ON statement can take different forms based upon the type of
* the object associated with the comment. The form of the statement is:
*
! * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW |
* CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
* CAST | COLUMN | SCHEMA | TABLESPACE | ROLE |
* TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY |
--- 4118,4124 ----
* The COMMENT ON statement can take different forms based upon the type of
* the object associated with the comment. The form of the statement is:
*
! * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | [MATERIALIZED] VIEW |
* CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT |
* CAST | COLUMN | SCHEMA | TABLESPACE | ROLE |
* TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY |
***************
*** 4281,4286 ****
--- 4297,4303 ----
| DOMAIN_P { $$ = OBJECT_TYPE; }
| TYPE_P { $$ = OBJECT_TYPE; }
| VIEW { $$ = OBJECT_VIEW; }
+ | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
| CONVERSION_P { $$ = OBJECT_CONVERSION; }
| TABLESPACE { $$ = OBJECT_TABLESPACE; }
| ROLE { $$ = OBJECT_ROLE; }
***************
*** 5691,5696 ****
--- 5708,5722 ----
n->newname = $6;
$$ = (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;
+ $$ = (Node *)n;
+ }
| ALTER INDEX qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
***************
*** 5867,5872 ****
--- 5893,5906 ----
n->newschema = $6;
$$ = (Node *)n;
}
+ | ALTER MATERIALIZED VIEW qualified_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_MATVIEW;
+ n->relation = $4;
+ n->newschema = $7;
+ $$ = (Node *)n;
+ }
| ALTER TYPE_P any_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
***************
*** 6319,6353 ****
/*****************************************************************************
*
* QUERY:
! * CREATE [ OR REPLACE ] [ TEMP ] VIEW <viewname> '('target-list ')'
* AS <query> [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
*
*****************************************************************************/
! ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $4;
n->view->istemp = $2;
! n->aliases = $5;
! n->query = $7;
n->replace = false;
$$ = (Node *) n;
}
! | CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $6;
n->view->istemp = $4;
! n->aliases = $7;
! n->query = $9;
n->replace = true;
$$ = (Node *) n;
}
;
opt_check_option:
WITH CHECK OPTION
{
--- 6353,6394 ----
/*****************************************************************************
*
* QUERY:
! * CREATE [ OR REPLACE ] [ TEMP ] [ MATERIALIZED ] VIEW <viewname> '('target-list ')'
* AS <query> [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
*
*****************************************************************************/
! ViewStmt: CREATE OptTemp OptMaterialized VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $5;
n->view->istemp = $2;
! n->ismaterialized = $3;
! n->aliases = $6;
! n->query = $8;
n->replace = false;
$$ = (Node *) n;
}
! | CREATE OR REPLACE OptTemp OptMaterialized VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $7;
n->view->istemp = $4;
! n->ismaterialized = $5;
! n->aliases = $8;
! n->query = $10;
n->replace = true;
$$ = (Node *) n;
}
;
+
+ OptMaterialized: MATERIALIZED { $$ = true; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
opt_check_option:
WITH CHECK OPTION
{
***************
*** 7577,7582 ****
--- 7618,7624 ----
$$->options = NIL;
$$->onCommit = ONCOMMIT_NOOP;
$$->tableSpaceName = NULL;
+ $$->isrefresh = FALSE;
}
| /*EMPTY*/
{ $$ = NULL; }
***************
*** 10982,10987 ****
--- 11024,11030 ----
| REASSIGN
| RECHECK
| RECURSIVE
+ | REFRESH
| REINDEX
| RELATIVE_P
| RELEASE
***************
*** 11211,11216 ****
--- 11254,11260 ----
| LIMIT
| LOCALTIME
| LOCALTIMESTAMP
+ | MATERIALIZED
| NOT
| NULL_P
| OFF
*** ./src/backend/rewrite/rewriteDefine.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/rewrite/rewriteDefine.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 159,165 ****
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced,
! (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
/*
* Also install dependencies on objects referenced in action and qual.
--- 159,165 ----
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced,
! (evtype == CMD_SELECT || evtype == CMD_REFRESH) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
/*
* Also install dependencies on objects referenced in action and qual.
***************
*** 246,252 ****
* 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",
--- 246,253 ----
* 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 &&
! event_relation->rd_rel->relkind != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or view",
***************
*** 288,294 ****
errhint("Use triggers instead.")));
}
! if (event_type == CMD_SELECT)
{
/*
* Rules ON SELECT are restricted to view definitions
--- 289,295 ----
errhint("Use triggers instead.")));
}
! if (event_type == CMD_SELECT || event_type == CMD_REFRESH)
{
/*
* Rules ON SELECT are restricted to view definitions
***************
*** 313,319 ****
* ... the one action must be a SELECT, ...
*/
query = (Query *) linitial(action);
! if (!is_instead ||
query->commandType != CMD_SELECT ||
query->utilityStmt != NULL ||
query->intoClause != NULL)
--- 314,320 ----
* ... the one action must be a SELECT, ...
*/
query = (Query *) linitial(action);
! if ((!is_instead && (event_relation->rd_rel->relkind != RELKIND_MATVIEW)) ||
query->commandType != CMD_SELECT ||
query->utilityStmt != NULL ||
query->intoClause != NULL)
***************
*** 349,355 ****
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",
--- 350,356 ----
RewriteRule *rule;
rule = event_relation->rd_rules->rules[i];
! if (rule->event == CMD_SELECT || rule->event == CMD_REFRESH)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("\"%s\" is already a view",
***************
*** 499,505 ****
*
* XXX what about getting rid of its TOAST table? For now, we don't.
*/
! if (RelisBecomingView)
RelationDropStorage(event_relation);
/* Close rel, but keep lock till commit... */
--- 500,506 ----
*
* XXX what about getting rid of its TOAST table? For now, we don't.
*/
! if (RelisBecomingView && (event_relation->rd_rel->relkind != RELKIND_MATVIEW))
RelationDropStorage(event_relation);
/* Close rel, but keep lock till commit... */
*** ./src/backend/rewrite/rewriteHandler.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/rewrite/rewriteHandler.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 1390,1396 ****
for (i = 0; i < rules->numLocks; i++)
{
rule = rules->rules[i];
! if (rule->event != CMD_SELECT)
continue;
if (rule->attrno > 0)
--- 1390,1396 ----
for (i = 0; i < rules->numLocks; i++)
{
rule = rules->rules[i];
! if (rule->event != CMD_SELECT && rule->event != CMD_REFRESH)
continue;
if (rule->attrno > 0)
***************
*** 1422,1427 ****
--- 1422,1441 ----
{
rule = lfirst(l);
+ /*
+ * There are two cases when we want to prevent firing rule:
+ * 1) command is just SELECT stmt
+ * 2) commant is SELECT INTO stmt, not REFRESHing MV
+ */
+ if ((rule->event == CMD_REFRESH && parsetree->intoClause == NULL)
+ ||
+ (rule->event == CMD_REFRESH &&
+ parsetree->intoClause != NULL &&
+ !parsetree->intoClause->isrefresh))
+ {
+ continue;
+ }
+
parsetree = ApplyRetrieveRule(parsetree,
rule,
rt_index,
***************
*** 1659,1664 ****
--- 1673,1708 ----
rt_entry_relation = heap_open(rt_entry->relid, NoLock);
/*
+ * Inserting, updating or deleting row in materilized views are not allowed
+ */
+ if (rt_entry_relation->rd_rel->relkind == RELKIND_MATVIEW)
+ {
+ heap_close(rt_entry_relation, NoLock);
+ switch (parsetree->commandType)
+ {
+ case CMD_INSERT:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot insert into a materialized view")));
+ break;
+ case CMD_UPDATE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot update a materialized view")));
+ break;
+ case CMD_DELETE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot delete from a materialized view")));
+ break;
+ default:
+ elog(ERROR, "unrecognized commandType: %d",
+ (int) parsetree->commandType);
+ break;
+ }
+ }
+
+ /*
* If it's an INSERT or UPDATE, rewrite the targetlist into standard
* form. This will be needed by the planner anyway, and doing it now
* ensures that any references to NEW.field will behave sanely.
*** ./src/backend/rewrite/rewriteSupport.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/rewrite/rewriteSupport.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 69,75 ****
{
/* Do the update */
classForm->relhasrules = relHasRules;
! if (relIsBecomingView)
classForm->relkind = RELKIND_VIEW;
simple_heap_update(relationRelation, &tuple->t_self, tuple);
--- 69,77 ----
{
/* Do the update */
classForm->relhasrules = relHasRules;
!
! /* do not change RELKIND if its Materialized View */
! if (relIsBecomingView && classForm->relkind != RELKIND_MATVIEW)
classForm->relkind = RELKIND_VIEW;
simple_heap_update(relationRelation, &tuple->t_self, tuple);
*** ./src/backend/tcop/utility.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/tcop/utility.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 606,611 ****
--- 606,612 ----
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
+ case OBJECT_MATVIEW:
case OBJECT_INDEX:
RemoveRelations(stmt);
break;
***************
*** 1544,1549 ****
--- 1545,1553 ----
case OBJECT_VIEW:
tag = "DROP VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "DROP MATERIALIZED VIEW";
+ break;
case OBJECT_INDEX:
tag = "DROP INDEX";
break;
***************
*** 1637,1642 ****
--- 1641,1649 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "ALTER MATERIALIZED VIEW";
+ break;
case OBJECT_TSPARSER:
tag = "ALTER TEXT SEARCH PARSER";
break;
***************
*** 1694,1699 ****
--- 1701,1709 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "ALTER MATERIALIZED VIEW";
+ break;
default:
tag = "???";
break;
***************
*** 1775,1780 ****
--- 1785,1793 ----
case OBJECT_VIEW:
tag = "ALTER VIEW";
break;
+ case OBJECT_MATVIEW:
+ tag = "ALTER MATERIALIZED VIEW";
+ break;
default:
tag = "???";
break;
***************
*** 1847,1853 ****
break;
case T_ViewStmt:
! tag = "CREATE VIEW";
break;
case T_CreateFunctionStmt:
--- 1860,1870 ----
break;
case T_ViewStmt:
! {
! ViewStmt *stmt = (ViewStmt *) parsetree;
!
! tag = (stmt->ismaterialized) ? "CREATE MATERIALIZED VIEW" : "CREATE VIEW";
! }
break;
case T_CreateFunctionStmt:
*** ./src/backend/utils/adt/dbsize.c.orig 2010-07-06 10:43:16.000000000 +0200
--- ./src/backend/utils/adt/dbsize.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 541,546 ****
--- 541,547 ----
case RELKIND_INDEX:
case RELKIND_SEQUENCE:
case RELKIND_TOASTVALUE:
+ case RELKIND_MATVIEW:
/* okay, these have storage */
if (relform->relfilenode)
result = relform->relfilenode;
***************
*** 588,593 ****
--- 589,595 ----
case RELKIND_INDEX:
case RELKIND_SEQUENCE:
case RELKIND_TOASTVALUE:
+ case RELKIND_MATVIEW:
/* okay, these have storage */
/* This logic should match RelationInitPhysicalAddr */
*** ./src/backend/utils/adt/ruleutils.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/backend/utils/adt/ruleutils.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 2384,2391 ****
query = (Query *) linitial(actions);
! if (ev_type != '1' || ev_attr >= 0 || !is_instead ||
! strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT)
{
appendStringInfo(buf, "Not a view");
return;
--- 2384,2393 ----
query = (Query *) linitial(actions);
! /* ev_type == 1 SELECT, ev_type == 6 REFRESH */
! if (ev_attr >= 0 || strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT ||
! ((!is_instead || ev_type != '1') &&
! (is_instead || ev_type != '6')))
{
appendStringInfo(buf, "Not a view");
return;
*** ./src/bin/pg_dump/pg_backup_archiver.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/bin/pg_dump/pg_backup_archiver.c 2010-07-08 21:10:12.000000000 +0200
***************
*** 2699,2705 ****
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 */
--- 2699,2705 ----
const char *type = te->desc;
/* Use ALTER TABLE for views and sequences */
! if (strcmp(type, "VIEW") == 0 || strcmp(type, "MATERIALIZED VIEW") == 0 || strcmp(type, "SEQUENCE") == 0)
type = "TABLE";
/* objects named by a schema and name */
***************
*** 2898,2903 ****
--- 2898,2904 ----
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, "TEXT SEARCH DICTIONARY") == 0 ||
strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
***************
*** 3010,3015 ****
--- 3011,3017 ----
AH->version = ((AH->vmaj * 256 + AH->vmin) * 256 + AH->vrev) * 256 + 0;
+
if (AH->version < K_VERS_1_0 || AH->version > K_VERS_MAX)
die_horribly(AH, modulename, "unsupported version (%d.%d) in file header\n",
AH->vmaj, AH->vmin);
*** ./src/bin/pg_dump/pg_dump.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/bin/pg_dump/pg_dump.c 2010-07-08 21:12:23.000000000 +0200
***************
*** 1434,1439 ****
--- 1434,1442 ----
/* Skip VIEWs (no data to dump) */
if (tblinfo[i].relkind == RELKIND_VIEW)
continue;
+ /* Skip MATERIALIZED VIEWs (redundant data are not dumped) */
+ if (tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
/* Skip SEQUENCEs (handled elsewhere) */
if (tblinfo[i].relkind == RELKIND_SEQUENCE)
continue;
***************
*** 3464,3475 ****
"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') "
"ORDER BY c.oid",
username_subquery,
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
! RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
}
else if (g_fout->remoteVersion >= 80400)
{
--- 3467,3478 ----
"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_MATVIEW, RELKIND_COMPOSITE_TYPE);
}
else if (g_fout->remoteVersion >= 80400)
{
***************
*** 4515,4522 ****
* 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,
ruleinfo[i].dobj.dumpId);
--- 4518,4528 ----
* 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) ||
! (ruleinfo[i].ruletable->relkind == RELKIND_MATVIEW &&
! ruleinfo[i].ev_type == '6' && !ruleinfo[i].is_instead)
! )
{
addObjectDependency(&ruleinfo[i].ruletable->dobj,
ruleinfo[i].dobj.dumpId);
***************
*** 10574,10579 ****
--- 10580,10639 ----
PQclear(res);
}
+ else if (tbinfo->relkind == RELKIND_MATVIEW)
+ {
+ char *viewdef;
+
+ reltypename = "MATERIALIZED VIEW";
+
+ /* Fetch the view definition */
+ appendPQExpBuffer(query,
+ "SELECT pg_catalog.pg_get_viewdef('%u'::pg_catalog.oid) AS viewdef",
+ tbinfo->dobj.catId.oid);
+
+ res = PQexec(g_conn, query->data);
+ check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != 1)
+ {
+ if (PQntuples(res) < 1)
+ write_msg(NULL, "query to obtain definition of view \"%s\" returned no data\n",
+ tbinfo->dobj.name);
+ else
+ write_msg(NULL, "query to obtain definition of view \"%s\" returned more than one definition\n",
+ tbinfo->dobj.name);
+ exit_nicely();
+ }
+
+ viewdef = PQgetvalue(res, 0, 0);
+
+ if (strlen(viewdef) == 0)
+ {
+ write_msg(NULL, "definition of view \"%s\" appears to be empty (length zero)\n",
+ tbinfo->dobj.name);
+ exit_nicely();
+ }
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "DROP MATERIALIZED VIEW %s.",
+ fmtId(tbinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s;\n",
+ fmtId(tbinfo->dobj.name));
+
+ if (binary_upgrade)
+ binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
+
+ appendPQExpBuffer(q, "CREATE MATERIALIZED VIEW %s AS\n %s\n\n",
+ fmtId(tbinfo->dobj.name), viewdef);
+
+ appendPQExpBuffer(q, "ALTER MATERIALIZED VIEW %s REFRESH;\n",
+ fmtId(tbinfo->dobj.name));
+
+ PQclear(res);
+ }
else
{
reltypename = "TABLE";
*** ./src/bin/pg_dump/pg_dump_sort.c.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/bin/pg_dump/pg_dump_sort.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 823,829 ****
return;
}
! /* Indirect loop involving view and ON SELECT rule */
if (nLoop > 2)
{
for (i = 0; i < nLoop; i++)
--- 823,854 ----
return;
}
! /* Materialized View and its ON REFRESH rule */
! if (nLoop == 2 &&
! loop[0]->objType == DO_TABLE &&
! loop[1]->objType == DO_RULE &&
! ((RuleInfo *) loop[1])->ev_type == '6' &&
! !((RuleInfo *) loop[1])->is_instead &&
! ((RuleInfo *) loop[1])->ruletable == (TableInfo *) loop[0])
! {
! repairViewRuleLoop(loop[0], loop[1]);
! return;
! }
! if (nLoop == 2 &&
! loop[1]->objType == DO_TABLE &&
! loop[0]->objType == DO_RULE &&
! ((RuleInfo *) loop[0])->ev_type == '6' &&
! !((RuleInfo *) loop[0])->is_instead &&
! ((RuleInfo *) loop[0])->ruletable == (TableInfo *) loop[1])
! {
! repairViewRuleLoop(loop[1], loop[0]);
! return;
! }
!
! /*
! * Indirect loop involving view and ON SELECT rule
! * and materialized view and its ON REFRESH rule
! */
if (nLoop > 2)
{
for (i = 0; i < nLoop; i++)
***************
*** 832,837 ****
--- 857,863 ----
{
for (j = 0; j < nLoop; j++)
{
+ /* view and ON SELECT rule */
if (loop[j]->objType == DO_RULE &&
((RuleInfo *) loop[j])->ev_type == '1' &&
((RuleInfo *) loop[j])->is_instead &&
***************
*** 840,845 ****
--- 866,880 ----
repairViewRuleMultiLoop(loop[i], loop[j]);
return;
}
+ /* materialized view and ON REFRESH rule */
+ if (loop[j]->objType == DO_RULE &&
+ ((RuleInfo *) loop[j])->ev_type == '6' &&
+ !((RuleInfo *) loop[j])->is_instead &&
+ ((RuleInfo *) loop[j])->ruletable == (TableInfo *) loop[i])
+ {
+ repairViewRuleMultiLoop(loop[i], loop[j]);
+ return;
+ }
}
}
}
*** ./src/bin/psql/command.c.orig 2010-07-06 10:43:17.000000000 +0200
--- ./src/bin/psql/command.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 346,352 ****
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvs", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
--- 346,352 ----
success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
! success = listTables("tvms", NULL, show_verbose, show_system);
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
***************
*** 407,412 ****
--- 407,413 ----
break;
case 't':
case 'v':
+ case 'm':
case 'i':
case 's':
success = listTables(&cmd[1], pattern, show_verbose, show_system);
*** ./src/bin/psql/describe.c.orig 2010-07-06 10:43:17.000000000 +0200
--- ./src/bin/psql/describe.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 911,923 ****
" n.nspname as nspname,\n"
" CAST(c.relname AS pg_catalog.text) as name,\n"
" CAST(\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END"
" AS pg_catalog.text) as object\n"
" FROM 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', 'i', 'S')\n",
gettext_noop("table"),
gettext_noop("view"),
gettext_noop("index"),
gettext_noop("sequence"));
--- 911,924 ----
" n.nspname as nspname,\n"
" CAST(c.relname AS pg_catalog.text) as name,\n"
" CAST(\n"
! " CASE c.relkind WHEN 'r' THEN '%s' WHEN 'v' THEN '%s' WHEN 'm' THEN '%s' WHEN 'i' THEN '%s' WHEN 'S' THEN '%s' END"
" AS pg_catalog.text) as object\n"
" FROM 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', 'i', 'S')\n",
gettext_noop("table"),
gettext_noop("view"),
+ gettext_noop("materialized view"),
gettext_noop("index"),
gettext_noop("sequence"));
***************
*** 1266,1271 ****
--- 1267,1276 ----
printfPQExpBuffer(&title, _("View \"%s.%s\""),
schemaname, relationname);
break;
+ case 'm':
+ printfPQExpBuffer(&title, _("Materialized View \"%s.%s\""),
+ schemaname, relationname);
+ break;
case 'S':
printfPQExpBuffer(&title, _("Sequence \"%s.%s\""),
schemaname, relationname);
***************
*** 1299,1305 ****
headers[1] = gettext_noop("Type");
cols = 2;
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
--- 1304,1310 ----
headers[1] = gettext_noop("Type");
cols = 2;
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || tableinfo.relkind == 'm')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
***************
*** 1325,1331 ****
printTableAddHeader(&cont, headers[i], true, 'l');
/* Check if table is a view */
! if (tableinfo.relkind == 'v' && verbose)
{
PGresult *result;
--- 1330,1336 ----
printTableAddHeader(&cont, headers[i], true, 'l');
/* Check if table is a view */
! if ((tableinfo.relkind == 'v' || tableinfo.relkind == 'm') && verbose)
{
PGresult *result;
***************
*** 1527,1533 ****
PQclear(result);
}
}
! else if (tableinfo.relkind == 'r')
{
/* Footer information about a table */
PGresult *result = NULL;
--- 1532,1538 ----
PQclear(result);
}
}
! if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm')
{
/* Footer information about a table */
PGresult *result = NULL;
***************
*** 1724,1730 ****
/* print rules */
if (tableinfo.hasrules)
{
! if (pset.sversion >= 80300)
{
printfPQExpBuffer(&buf,
"SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
--- 1729,1744 ----
/* print rules */
if (tableinfo.hasrules)
{
! if (pset.sversion >= 90000)
! {
! printfPQExpBuffer(&buf,
! "SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
! "ev_enabled\n"
! "FROM pg_catalog.pg_rewrite r\n"
! "WHERE r.ev_class = '%s' AND r.rulename <> '_RETURN' ORDER BY 1",
! oid);
! }
! else if (pset.sversion >= 80300)
{
printfPQExpBuffer(&buf,
"SELECT r.rulename, trim(trailing ';' from pg_catalog.pg_get_ruledef(r.oid, true)), "
***************
*** 2006,2012 ****
}
/* OIDs and options */
! if (verbose)
{
const char *s = _("Has OIDs");
--- 2020,2026 ----
}
/* OIDs and options */
! if (verbose && tableinfo.relkind != 'm')
{
const char *s = _("Has OIDs");
***************
*** 2079,2085 ****
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
--- 2093,2099 ----
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
! if (relkind == 'r' || relkind == 'i' || relkind == 'm')
{
/*
* We ignore the database default tablespace so that users not using
***************
*** 2339,2344 ****
--- 2353,2359 ----
* t - tables
* i - indexes
* v - views
+ * m - materialized views
* s - sequences
* (any order of the above is fine)
* If tabtypes is empty, we default to \dtvs.
***************
*** 2349,2354 ****
--- 2364,2370 ----
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;
PQExpBufferData buf;
***************
*** 2356,2363 ****
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showSeq))
! showTables = showViews = showSeq = true;
initPQExpBuffer(&buf);
--- 2372,2379 ----
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false, false, false};
! if (!(showTables || showIndexes || showViews || showMatViews || showSeq))
! showTables = showViews = showMatViews = showSeq = true;
initPQExpBuffer(&buf);
***************
*** 2368,2379 ****
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' 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"),
--- 2384,2396 ----
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' 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"),
***************
*** 2407,2412 ****
--- 2424,2431 ----
appendPQExpBuffer(&buf, "'r',");
if (showViews)
appendPQExpBuffer(&buf, "'v',");
+ if (showMatViews)
+ appendPQExpBuffer(&buf, "'m',");
if (showIndexes)
appendPQExpBuffer(&buf, "'i',");
if (showSeq)
*** ./src/bin/psql/help.c.orig 2010-07-06 10:43:17.000000000 +0200
--- ./src/bin/psql/help.c 2010-07-08 15:22:15.000000000 +0200
***************
*** 194,200 ****
fprintf(output, _("Informational\n"));
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
! fprintf(output, _(" \\d[S+] list tables, views, and sequences\n"));
fprintf(output, _(" \\d[S+] NAME describe table, view, sequence, or index\n"));
fprintf(output, _(" \\da[S] [PATTERN] list aggregates\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
--- 194,200 ----
fprintf(output, _("Informational\n"));
fprintf(output, _(" (options: S = show system objects, + = additional detail)\n"));
! fprintf(output, _(" \\d[S+] list tables, views, materialized views, and sequences\n"));
fprintf(output, _(" \\d[S+] NAME describe table, view, sequence, or index\n"));
fprintf(output, _(" \\da[S] [PATTERN] list aggregates\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
***************
*** 223,228 ****
--- 223,229 ----
fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n"));
fprintf(output, _(" \\du[+] [PATTERN] list roles (users)\n"));
fprintf(output, _(" \\dv[S+] [PATTERN] list views\n"));
+ fprintf(output, _(" \\dm[S+] [PATTERN] list materialized views\n"));
fprintf(output, _(" \\l[+] list all databases\n"));
fprintf(output, _(" \\z [PATTERN] same as \\dp\n"));
fprintf(output, "\n");
*** ./src/include/catalog/pg_class.h.orig 2010-07-08 20:32:53.000000000 +0200
--- ./src/include/catalog/pg_class.h 2010-07-08 15:22:15.000000000 +0200
***************
*** 147,152 ****
--- 147,153 ----
#define RELKIND_UNCATALOGED 'u' /* temporary heap */
#define RELKIND_TOASTVALUE 't' /* moved off huge values */
#define RELKIND_VIEW 'v' /* view */
+ #define RELKIND_MATVIEW 'm' /* materialized view */
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#endif /* PG_CLASS_H */
*** ./src/include/nodes/nodes.h.orig 2010-07-08 20:32:54.000000000 +0200
--- ./src/include/nodes/nodes.h 2010-07-08 15:22:15.000000000 +0200
***************
*** 512,517 ****
--- 512,518 ----
CMD_DELETE,
CMD_UTILITY, /* cmds like create, destroy, copy, vacuum,
* etc. */
+ CMD_REFRESH, /* refreshing tables, like materialized views */
CMD_NOTHING /* dummy command for instead nothing rules
* with qual */
} CmdType;
*** ./src/include/nodes/parsenodes.h.orig 2010-07-08 20:32:54.000000000 +0200
--- ./src/include/nodes/parsenodes.h 2010-07-08 15:22:15.000000000 +0200
***************
*** 1055,1060 ****
--- 1055,1061 ----
OBJECT_INDEX,
OBJECT_LANGUAGE,
OBJECT_LARGEOBJECT,
+ OBJECT_MATVIEW,
OBJECT_OPCLASS,
OBJECT_OPERATOR,
OBJECT_OPFAMILY,
***************
*** 1150,1156 ****
AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */
AT_DisableRule, /* DISABLE RULE name */
AT_AddInherit, /* INHERIT parent */
! AT_DropInherit /* NO INHERIT parent */
} AlterTableType;
typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
--- 1151,1158 ----
AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */
AT_DisableRule, /* DISABLE RULE name */
AT_AddInherit, /* INHERIT parent */
! AT_DropInherit, /* NO INHERIT parent */
! AT_Refresh /* alter materialized view REFRESH */
} AlterTableType;
typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
***************
*** 2181,2186 ****
--- 2183,2189 ----
List *aliases; /* target column names */
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
+ bool ismaterialized; /* materialize view? */
} ViewStmt;
/* ----------------------
*** ./src/include/nodes/primnodes.h.orig 2010-07-08 20:32:54.000000000 +0200
--- ./src/include/nodes/primnodes.h 2010-07-08 15:22:15.000000000 +0200
***************
*** 91,96 ****
--- 91,97 ----
List *options; /* options from WITH clause */
OnCommitAction onCommit; /* what do we do at COMMIT? */
char *tableSpaceName; /* table space to use, or NULL */
+ bool isrefresh; /* refreshing table? */
} IntoClause;
*** ./src/include/parser/kwlist.h.orig 2010-07-08 20:32:54.000000000 +0200
--- ./src/include/parser/kwlist.h 2010-07-08 15:22:15.000000000 +0200
***************
*** 229,234 ****
--- 229,235 ----
PG_KEYWORD("login", LOGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
+ PG_KEYWORD("materialized", MATERIALIZED, RESERVED_KEYWORD)
PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
***************
*** 302,307 ****
--- 303,309 ----
PG_KEYWORD("recheck", RECHECK, UNRESERVED_KEYWORD)
PG_KEYWORD("recursive", RECURSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD)
+ PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD)
PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD)
2010/7/8 Pavel Baroš <baros.p@seznam.cz>:
Description of patch:
1) can create MV, and is created uninitialized with data
CREATE MATERIALIZED VIEW mvname AS SELECT ...
This doesn't seem acceptable. It should populate it on creation.
2) can refresh MV
ALTER MATERIALIZED VIEW mvname REFRESH3) MV cannot be modified by DML commands (INSERT, UPDATE and DELETE are not
permitted)4) index can be created and used with MV
5) pg_dump is repaired, in previous patch dump threw error, now dont, but it
is sort of dummy, I want to reach state, where refreshing command will be
posed after all COPY statements (when all data are in tables). In this patch
REFRESH command is right behind CREATE MV command.
Hmm... ISTM that you probably need some kind of dependency stuff in
here to make the materialized view get created after the tables it
depends on have been populated with data. It needs to work with
parallel restore, too. I'm not sure exactly how the dependency stuff
in pg_dump works, though.
A subtle point here is that if you dump and restore a database
containing a materialized view, the new database might not be quite
the same as the old one, because the materialized view might have been
out of date before, and when you recreate it, it'll get refreshed.
I'm not sure there's much we can/should do about that, though.
6) psql works too, new command \dm[S+] was added to the list
\d[S+] [PATTERN] - lists all db objects like tables, view, materialized
view and sequences
\dm[S+] [PATTERN] - lists all materialized views7) there are some docs too, but I guess it is not enough, at least my
english will need to correct
If we're going to treat materialized views as a separate object type,
you probably need to break out the docs for CREATE MATERIALIZED VIEW,
ALTER MATERIALIZED VIEW, and DROP MATERIALIZED VIEW into their own
pages, rather than having then mixed up with corresponding pages for
regular views.
8) some ALTER TABLE commands works, ie. RENAME TO, OWNER TO, SET SCHEMA, SET
TABLESPACE9) MV and columns can be commented
10) also some functions behave as expected, but if you know about some I did
not mention and could fail when used with MV, I appreciate your hints
pg_get_viewdef()
pg_get_ruledef()
pg_relation_filenode()
pg_relation_filepath()
pg_table_size()In progress:
- regression tests
- behavior of various ALTER commands, ie SET STATISTIC, CLUSTER ON,
ENABLE/DISABLE RULE, etc.
This isn't right:
rhaas=# create view v as select * from t;
CREATE VIEW
rhaas=# alter view v refresh;
ERROR: unrecognized alter table type: 41
Please add your patch here, so that it will be reviewed during the
about-to-begin CommitFest.
https://commitfest.postgresql.org/action/commitfest_view/open
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise Postgres Company
Dne 9.7.2010 21:33, Robert Haas napsal(a):
2010/7/8 Pavel Baroďż˝<baros.p@seznam.cz>:
Description of patch:
1) can create MV, and is created uninitialized with data
CREATE MATERIALIZED VIEW mvname AS SELECT ...This doesn't seem acceptable. It should populate it on creation.
Yes, it would be better, in addition, true is, this behavior will be
required if is expected to implement incremental MV in the close future.
2) can refresh MV
ALTER MATERIALIZED VIEW mvname REFRESH3) MV cannot be modified by DML commands (INSERT, UPDATE and DELETE are not
permitted)4) index can be created and used with MV
5) pg_dump is repaired, in previous patch dump threw error, now dont, but it
is sort of dummy, I want to reach state, where refreshing command will be
posed after all COPY statements (when all data are in tables). In this patch
REFRESH command is right behind CREATE MV command.Hmm... ISTM that you probably need some kind of dependency stuff in
here to make the materialized view get created after the tables it
depends on have been populated with data. It needs to work with
parallel restore, too. I'm not sure exactly how the dependency stuff
in pg_dump works, though.
never mind in case MV will be populated on creation.
A subtle point here is that if you dump and restore a database
containing a materialized view, the new database might not be quite
the same as the old one, because the materialized view might have been
out of date before, and when you recreate it, it'll get refreshed.
I'm not sure there's much we can/should do about that, though.
yes, it is interesting, of course, there can be real-life example, where
population on creating is needed and is not, and I'm thinking of
solution similar to Oracle or DB2. Add some option to creating MV, that
enable/disable population on creating:
http://www.ibm.com/developerworks/data/library/techarticle/dm-0708khatri/
Oracle:
CREATE MATERIALIZED VIEW mvname
[ BUILD [IMMEDIATE | DEFERRED] ]
AS SELECT ..
DB2:
CREATE TABLE mvname
AS SELECT ...
[ INITIALLY DEFERRED | IMMEDIATE ]
6) psql works too, new command \dm[S+] was added to the list
\d[S+] [PATTERN] - lists all db objects like tables, view, materialized
view and sequences
\dm[S+] [PATTERN] - lists all materialized views
I also noticed I forgot handle options \dp and \dpp, this should be OK
in next version of patch.
7) there are some docs too, but I guess it is not enough, at least my
english will need to correctIf we're going to treat materialized views as a separate object type,
you probably need to break out the docs for CREATE MATERIALIZED VIEW,
ALTER MATERIALIZED VIEW, and DROP MATERIALIZED VIEW into their own
pages, rather than having then mixed up with corresponding pages for
regular views.
Yeah, that was problem I just solved like that here, but I confess this
would be better.
In progress:
- regression tests
- behavior of various ALTER commands, ie SET STATISTIC, CLUSTER ON,
ENABLE/DISABLE RULE, etc.This isn't right:
rhaas=# create view v as select * from t;
CREATE VIEW
rhaas=# alter view v refresh;
ERROR: unrecognized alter table type: 41
I know, cases like that will be more than that. Thats why I work on good
tests now.
Please add your patch here, so that it will be reviewed during the
about-to-begin CommitFest.https://commitfest.postgresql.org/action/commitfest_view/open
OK, but will you help me with that form? Do you think I can fill it like
that? I'm not sure about few fields ..
Name: Snapshot materialized views
CommitFest Topic: [ Miscellaneous | SQL Features ] ???
Patch Status: Needs review
Author: me
Reviewers: You?
Commiters: who?
and I quess fields 'Date Closed' and 'Message-ID for Original Patch'
will be filled later.
thanks a lot
Pavel Baros
Dne 9.7.2010 21:33, Robert Haas napsal(a):
2010/7/8 Pavel Baroďż˝<baros.p@seznam.cz>:
Description of patch:
1) can create MV, and is created uninitialized with data
CREATE MATERIALIZED VIEW mvname AS SELECT ...This doesn't seem acceptable. It should populate it on creation.
Yes, it would be better, in addition, true is, this behavior will be
required if is expected to implement incremental MV in the close future.
2) can refresh MV
ALTER MATERIALIZED VIEW mvname REFRESH3) MV cannot be modified by DML commands (INSERT, UPDATE and DELETE
are not
permitted)4) index can be created and used with MV
5) pg_dump is repaired, in previous patch dump threw error, now dont,
but it
is sort of dummy, I want to reach state, where refreshing command
will be
posed after all COPY statements (when all data are in tables). In
this patch
REFRESH command is right behind CREATE MV command.Hmm... ISTM that you probably need some kind of dependency stuff in
here to make the materialized view get created after the tables it
depends on have been populated with data. It needs to work with
parallel restore, too. I'm not sure exactly how the dependency stuff
in pg_dump works, though.
never mind in case MV will be populated on creation.
A subtle point here is that if you dump and restore a database
containing a materialized view, the new database might not be quite
the same as the old one, because the materialized view might have been
out of date before, and when you recreate it, it'll get refreshed.
I'm not sure there's much we can/should do about that, though.
yes, it is interesting, of course, there can be real-life example, where
population on creating is needed and is not, and I'm thinking of
solution similar to Oracle or DB2. Add some option to creating MV, that
enable/disable population on creating:
http://www.ibm.com/developerworks/data/library/techarticle/dm-0708khatri/
Oracle:
CREATE MATERIALIZED VIEW mvname
[ BUILD [IMMEDIATE | DEFERRED] ]
AS SELECT ..
DB2:
CREATE TABLE mvname
AS SELECT ...
[ INITIALLY DEFERRED | IMMEDIATE ]
6) psql works too, new command \dm[S+] was added to the list
\d[S+] [PATTERN] - lists all db objects like tables, view,
materialized
view and sequences
\dm[S+] [PATTERN] - lists all materialized views
I also noticed I forgot handle options \dp and \dpp, this should be OK
in next version of patch.
7) there are some docs too, but I guess it is not enough, at least my
english will need to correctIf we're going to treat materialized views as a separate object type,
you probably need to break out the docs for CREATE MATERIALIZED VIEW,
ALTER MATERIALIZED VIEW, and DROP MATERIALIZED VIEW into their own
pages, rather than having then mixed up with corresponding pages for
regular views.
Yeah, that was problem I just solved like that here, but I confess this
would be better.
In progress:
- regression tests
- behavior of various ALTER commands, ie SET STATISTIC, CLUSTER ON,
ENABLE/DISABLE RULE, etc.This isn't right:
rhaas=# create view v as select * from t;
CREATE VIEW
rhaas=# alter view v refresh;
ERROR: unrecognized alter table type: 41
I know, cases like that will be more than that. Thats why I work on good
tests now.
Please add your patch here, so that it will be reviewed during the
about-to-begin CommitFest.https://commitfest.postgresql.org/action/commitfest_view/open
OK, but will you help me with that form? Do you think I can fill it like
that? I'm not sure about few fields ..
Name: Snapshot materialized views
CommitFest Topic: [ Miscellaneous | SQL Features ] ???
Patch Status: Needs review
Author: me
Reviewers: You?
Commiters: who?
and I quess fields 'Date Closed' and 'Message-ID for Original Patch'
will be filled later.
thanks a lot
Pavel Baros
Pavel Baroᅵ<baros.p@seznam.cz> wrote:
Dne 9.7.2010 21:33, Robert Haas napsal(a):
Please add your patch here, so that it will be reviewed during
the about-to-begin CommitFest.https://commitfest.postgresql.org/action/commitfest_view/open
OK, but will you help me with that form? Do you think I can fill
it like that? I'm not sure about few fields ..Name: Snapshot materialized views
CommitFest Topic: [ Miscellaneous | SQL Features ] ???
SQL Features seems reasonable to me.
Patch Status: Needs review
Author: me
Reviewers: You?
Leave empty. Reviewers will sign up or be assigned.
Commiters: who?
That comes much later -- when the patch is complete and has a
favorable review, then a committer will pick it up.
and I quess fields 'Date Closed' and 'Message-ID for Original
Patch' will be filled later.
Date closed is only set for patches which are committed, returned
with feedback (for a later CommitFest), or rejected. When you make
an entry which references a post to the lists, you should fill in
the Message-ID from the email header of the post. You may be able
to get this from your email software as soon as you send the post;
if not, you can find it on the archive page for the post.
-Kevin
2010/7/12 Kevin Grittner <Kevin.Grittner@wicourts.gov>:
Pavel Baroš<baros.p@seznam.cz> wrote:
Dne 9.7.2010 21:33, Robert Haas napsal(a):
Please add your patch here, so that it will be reviewed during
the about-to-begin CommitFest.https://commitfest.postgresql.org/action/commitfest_view/open
OK, but will you help me with that form? Do you think I can fill
it like that? I'm not sure about few fields ..Name: Snapshot materialized views
CommitFest Topic: [ Miscellaneous | SQL Features ] ???SQL Features seems reasonable to me.
Patch Status: Needs review
Author: me
Reviewers: You?Leave empty. Reviewers will sign up or be assigned.
Commiters: who?
That comes much later -- when the patch is complete and has a
favorable review, then a committer will pick it up.and I quess fields 'Date Closed' and 'Message-ID for Original
Patch' will be filled later.Date closed is only set for patches which are committed, returned
with feedback (for a later CommitFest), or rejected. When you make
an entry which references a post to the lists, you should fill in
the Message-ID from the email header of the post. You may be able
to get this from your email software as soon as you send the post;
if not, you can find it on the archive page for the post.
This topic hasn't been touched on in nearly a year, but is the work
that's been done so far salvageable? I'm not sure what happens to
GSoC project work that doesn't get finished in time.
--
Thom Brown
Twitter: @darkixion
IRC (freenode): dark_ixion
Registered Linux user: #516935
EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company