Skip to content

Commit 02fa9ca

Browse files
author
Commitfest Bot
committed
[CF 5099] v7 - Add OR REPLACE option to CREATE MATERIALIZED VIEW
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5099 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/5cd7ec92-ee61-4080-8fb6-0aed6a51eeaf@ewie.name Author(s): Erik Wienhold
2 parents 87a350e + eccb932 commit 02fa9ca

File tree

11 files changed

+694
-98
lines changed

11 files changed

+694
-98
lines changed

doc/src/sgml/ref/create_materialized_view.sgml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ PostgreSQL documentation
2121

2222
<refsynopsisdiv>
2323
<synopsis>
24-
CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
24+
CREATE [ OR REPLACE ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
2525
[ (<replaceable>column_name</replaceable> [, ...] ) ]
2626
[ USING <replaceable class="parameter">method</replaceable> ]
2727
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
2828
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
2929
AS <replaceable>query</replaceable>
30-
[ WITH [ NO ] DATA ]
30+
[ WITH [ NO | OLD ] DATA ]
3131
</synopsis>
3232
</refsynopsisdiv>
3333

@@ -37,7 +37,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
3737
<para>
3838
<command>CREATE MATERIALIZED VIEW</command> defines a materialized view of
3939
a query. The query is executed and used to populate the view at the time
40-
the command is issued (unless <command>WITH NO DATA</command> is used) and may be
40+
the command is issued (unless <command>WITH NO DATA</command> or
41+
<command>WITH OLD DATA</command> is used) and may be
4142
refreshed later using <command>REFRESH MATERIALIZED VIEW</command>.
4243
</para>
4344

@@ -60,14 +61,25 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
6061
<title>Parameters</title>
6162

6263
<variablelist>
64+
<varlistentry>
65+
<term><literal>OR REPLACE</literal></term>
66+
<listitem>
67+
<para>
68+
Replaces a materialized view if it already exists.
69+
Specifying <literal>OR REPLACE</literal> together with
70+
<literal>IF NOT EXISTS</literal> is an error.
71+
</para>
72+
</listitem>
73+
</varlistentry>
74+
6375
<varlistentry>
6476
<term><literal>IF NOT EXISTS</literal></term>
6577
<listitem>
6678
<para>
6779
Do not throw an error if a materialized view with the same name already
6880
exists. A notice is issued in this case. Note that there is no guarantee
6981
that the existing materialized view is anything like the one that would
70-
have been created.
82+
have been created, unless you use <literal>OR REPLACE</literal> instead.
7183
</para>
7284
</listitem>
7385
</varlistentry>
@@ -151,14 +163,22 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
151163
</varlistentry>
152164

153165
<varlistentry>
154-
<term><literal>WITH [ NO ] DATA</literal></term>
166+
<term><literal>WITH [ NO | OLD ] DATA</literal></term>
155167
<listitem>
156168
<para>
157169
This clause specifies whether or not the materialized view should be
158170
populated at creation time. If not, the materialized view will be
159171
flagged as unscannable and cannot be queried until <command>REFRESH
160172
MATERIALIZED VIEW</command> is used.
161173
</para>
174+
175+
<para>
176+
The form <command>WITH OLD DATA</command> keeps the already stored data
177+
when replacing an existing materialized view to keep it populated. Use
178+
this form if you want to use <command>REFRESH MATERIALIZED VIEW CONCURRENTLY</command>
179+
as it requires a populated materialized view. It is an error to use this
180+
form when creating a new materialized view.
181+
</para>
162182
</listitem>
163183
</varlistentry>
164184

src/backend/commands/createas.c

Lines changed: 168 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -81,63 +81,154 @@ static void intorel_destroy(DestReceiver *self);
8181
static ObjectAddress
8282
create_ctas_internal(List *attrList, IntoClause *into)
8383
{
84-
CreateStmt *create = makeNode(CreateStmt);
85-
bool is_matview;
84+
bool is_matview,
85+
replace = false;
8686
char relkind;
87-
Datum toast_options;
88-
const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
87+
Oid matviewOid = InvalidOid;
8988
ObjectAddress intoRelationAddr;
9089

9190
/* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
9291
is_matview = (into->viewQuery != NULL);
9392
relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION;
9493

95-
/*
96-
* Create the target relation by faking up a CREATE TABLE parsetree and
97-
* passing it to DefineRelation.
98-
*/
99-
create->relation = into->rel;
100-
create->tableElts = attrList;
101-
create->inhRelations = NIL;
102-
create->ofTypename = NULL;
103-
create->constraints = NIL;
104-
create->options = into->options;
105-
create->oncommit = into->onCommit;
106-
create->tablespacename = into->tableSpaceName;
107-
create->if_not_exists = false;
108-
create->accessMethod = into->accessMethod;
94+
/* Check if an existing materialized view needs to be replaced. */
95+
if (is_matview)
96+
{
97+
LOCKMODE lockmode;
10998

110-
/*
111-
* Create the relation. (This will error out if there's an existing view,
112-
* so we don't need more code to complain if "replace" is false.)
113-
*/
114-
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
99+
lockmode = into->replace ? AccessExclusiveLock : NoLock;
100+
(void) RangeVarGetAndCheckCreationNamespace(into->rel, lockmode,
101+
&matviewOid);
102+
replace = OidIsValid(matviewOid) && into->replace;
103+
}
115104

116-
/*
117-
* If necessary, create a TOAST table for the target table. Note that
118-
* NewRelationCreateToastTable ends with CommandCounterIncrement(), so
119-
* that the TOAST table will be visible for insertion.
120-
*/
121-
CommandCounterIncrement();
105+
if (is_matview && replace)
106+
{
107+
Relation rel;
108+
List *atcmds = NIL;
109+
AlterTableCmd *atcmd;
110+
TupleDesc descriptor;
111+
112+
rel = relation_open(matviewOid, NoLock);
113+
114+
if (rel->rd_rel->relkind != RELKIND_MATVIEW)
115+
ereport(ERROR,
116+
errcode(ERRCODE_WRONG_OBJECT_TYPE),
117+
errmsg("\"%s\" is not a materialized view",
118+
RelationGetRelationName(rel)));
119+
120+
CheckTableNotInUse(rel, "CREATE OR REPLACE MATERIALIZED VIEW");
121+
122+
descriptor = BuildDescForRelation(attrList);
123+
checkViewColumns(descriptor, rel->rd_att, true);
124+
125+
/* add new attributes */
126+
if (list_length(attrList) > rel->rd_att->natts)
127+
{
128+
ListCell *c;
129+
int skip = rel->rd_att->natts;
130+
131+
foreach(c, attrList)
132+
{
133+
if (skip > 0)
134+
{
135+
skip--;
136+
continue;
137+
}
138+
atcmd = makeNode(AlterTableCmd);
139+
atcmd->subtype = AT_AddColumnToView;
140+
atcmd->def = (Node *) lfirst(c);
141+
atcmds = lappend(atcmds, atcmd);
142+
}
143+
}
144+
145+
/*
146+
* The following alters access method, tablespace, and storage options.
147+
* When replacing an existing matview we need to alter the relation
148+
* such that the defaults apply as if they have not been specified at
149+
* all by the CREATE statement.
150+
*/
151+
152+
/* access method */
153+
atcmd = makeNode(AlterTableCmd);
154+
atcmd->subtype = AT_SetAccessMethod;
155+
atcmd->name = into->accessMethod ? into->accessMethod : default_table_access_method;
156+
atcmds = lappend(atcmds, atcmd);
157+
158+
/* tablespace */
159+
atcmd = makeNode(AlterTableCmd);
160+
atcmd->subtype = AT_SetTableSpace;
161+
/* use empty string to specify default tablespace */
162+
atcmd->name = into->tableSpaceName ? into->tableSpaceName : "";
163+
atcmds = lappend(atcmds, atcmd);
164+
165+
/* storage options */
166+
atcmd = makeNode(AlterTableCmd);
167+
atcmd->subtype = AT_ReplaceRelOptions;
168+
atcmd->def = (Node *) into->options;
169+
atcmds = lappend(atcmds, atcmd);
170+
171+
AlterTableInternal(matviewOid, atcmds, true);
172+
CommandCounterIncrement();
173+
174+
relation_close(rel, NoLock);
175+
ObjectAddressSet(intoRelationAddr, RelationRelationId, matviewOid);
176+
}
177+
else
178+
{
179+
CreateStmt *create = makeNode(CreateStmt);
180+
Datum toast_options;
181+
const static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
122182

123-
/* parse and validate reloptions for the toast table */
124-
toast_options = transformRelOptions((Datum) 0,
125-
create->options,
126-
"toast",
127-
validnsps,
128-
true, false);
183+
/*
184+
* Create the target relation by faking up a CREATE TABLE parsetree
185+
* and passing it to DefineRelation.
186+
*/
187+
create->relation = into->rel;
188+
create->tableElts = attrList;
189+
create->inhRelations = NIL;
190+
create->ofTypename = NULL;
191+
create->constraints = NIL;
192+
create->options = into->options;
193+
create->oncommit = into->onCommit;
194+
create->tablespacename = into->tableSpaceName;
195+
create->if_not_exists = false;
196+
create->accessMethod = into->accessMethod;
129197

130-
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
198+
/*
199+
* Create the relation. (This will error out if there's an existing
200+
* view, so we don't need more code to complain if "replace" is
201+
* false.)
202+
*/
203+
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL,
204+
NULL);
131205

132-
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
206+
/*
207+
* If necessary, create a TOAST table for the target table. Note that
208+
* NewRelationCreateToastTable ends with CommandCounterIncrement(), so
209+
* that the TOAST table will be visible for insertion.
210+
*/
211+
CommandCounterIncrement();
212+
213+
/* parse and validate reloptions for the toast table */
214+
toast_options = transformRelOptions((Datum) 0,
215+
create->options,
216+
"toast",
217+
validnsps,
218+
true, false);
219+
220+
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
221+
222+
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
223+
}
133224

134225
/* Create the "view" part of a materialized view. */
135226
if (is_matview)
136227
{
137228
/* StoreViewQuery scribbles on tree, so make a copy */
138229
Query *query = copyObject(into->viewQuery);
139230

140-
StoreViewQuery(intoRelationAddr.objectId, query, false);
231+
StoreViewQuery(intoRelationAddr.objectId, query, replace);
141232
CommandCounterIncrement();
142233
}
143234

@@ -234,7 +325,34 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
234325

235326
/* Check if the relation exists or not */
236327
if (CreateTableAsRelExists(stmt))
328+
{
329+
/* An existing materialized view can be replaced. */
330+
if (is_matview && into->replace)
331+
{
332+
/* Change the relation to match the new query and other options. */
333+
address = create_ctas_nodata(query->targetList, into);
334+
335+
/*
336+
* Refresh the materialized view with a fake statement unless we
337+
* must keep the old data.
338+
*/
339+
if (!into->keepData)
340+
{
341+
RefreshMatViewStmt *refresh;
342+
343+
refresh = makeNode(RefreshMatViewStmt);
344+
refresh->relation = into->rel;
345+
refresh->skipData = into->skipData;
346+
refresh->concurrent = false;
347+
348+
address = ExecRefreshMatView(refresh, pstate->p_sourcetext, qc);
349+
}
350+
351+
return address;
352+
}
353+
237354
return InvalidObjectAddress;
355+
}
238356

239357
/*
240358
* Create the tuple receiver object and insert info it will need
@@ -273,6 +391,9 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
273391
*/
274392
if (is_matview)
275393
{
394+
if (into->keepData)
395+
elog(ERROR, "must not specify WITH OLD DATA when creating a new materialized view");
396+
276397
do_refresh = !into->skipData;
277398
into->skipData = true;
278399
}
@@ -402,26 +523,28 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas)
402523
oldrelid = get_relname_relid(into->rel->relname, nspid);
403524
if (OidIsValid(oldrelid))
404525
{
405-
if (!ctas->if_not_exists)
526+
if (!ctas->if_not_exists && !into->replace)
406527
ereport(ERROR,
407528
(errcode(ERRCODE_DUPLICATE_TABLE),
408529
errmsg("relation \"%s\" already exists",
409530
into->rel->relname)));
410531

411532
/*
412-
* The relation exists and IF NOT EXISTS has been specified.
533+
* The relation exists and IF NOT EXISTS or OR REPLACE has been
534+
* specified.
413535
*
414536
* If we are in an extension script, insist that the pre-existing
415537
* object be a member of the extension, to avoid security risks.
416538
*/
417539
ObjectAddressSet(address, RelationRelationId, oldrelid);
418540
checkMembershipInCurrentExtension(&address);
419541

420-
/* OK to skip */
421-
ereport(NOTICE,
422-
(errcode(ERRCODE_DUPLICATE_TABLE),
423-
errmsg("relation \"%s\" already exists, skipping",
424-
into->rel->relname)));
542+
if (ctas->if_not_exists)
543+
/* OK to skip */
544+
ereport(NOTICE,
545+
(errcode(ERRCODE_DUPLICATE_TABLE),
546+
errmsg("relation \"%s\" already exists, skipping",
547+
into->rel->relname)));
425548
return true;
426549
}
427550

0 commit comments

Comments
 (0)