Skip to content

Commit d79aaff

Browse files
ewieCommitfest Bot
authored andcommitted
Add OR REPLACE option to CREATE MATERIALIZED VIEW
1 parent 87a350e commit d79aaff

File tree

11 files changed

+624
-93
lines changed

11 files changed

+624
-93
lines changed

doc/src/sgml/ref/create_materialized_view.sgml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ 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>] [, ... ] ) ]
@@ -60,14 +60,25 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
6060
<title>Parameters</title>
6161

6262
<variablelist>
63+
<varlistentry>
64+
<term><literal>OR REPLACE</literal></term>
65+
<listitem>
66+
<para>
67+
Replaces a materialized view if it already exists.
68+
Specifying <literal>OR REPLACE</literal> together with
69+
<literal>IF NOT EXISTS</literal> is an error.
70+
</para>
71+
</listitem>
72+
</varlistentry>
73+
6374
<varlistentry>
6475
<term><literal>IF NOT EXISTS</literal></term>
6576
<listitem>
6677
<para>
6778
Do not throw an error if a materialized view with the same name already
6879
exists. A notice is issued in this case. Note that there is no guarantee
6980
that the existing materialized view is anything like the one that would
70-
have been created.
81+
have been created, unless you use <literal>OR REPLACE</literal> instead.
7182
</para>
7283
</listitem>
7384
</varlistentry>

src/backend/commands/createas.c

Lines changed: 174 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
#include "postgres.h"
2626

27+
#include "miscadmin.h"
2728
#include "access/heapam.h"
2829
#include "access/reloptions.h"
2930
#include "access/tableam.h"
@@ -34,6 +35,7 @@
3435
#include "commands/matview.h"
3536
#include "commands/prepare.h"
3637
#include "commands/tablecmds.h"
38+
#include "commands/tablespace.h"
3739
#include "commands/view.h"
3840
#include "executor/execdesc.h"
3941
#include "executor/executor.h"
@@ -81,63 +83,169 @@ static void intorel_destroy(DestReceiver *self);
8183
static ObjectAddress
8284
create_ctas_internal(List *attrList, IntoClause *into)
8385
{
84-
CreateStmt *create = makeNode(CreateStmt);
85-
bool is_matview;
86+
bool is_matview,
87+
replace = false;
8688
char relkind;
87-
Datum toast_options;
88-
const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
89+
Oid matviewOid = InvalidOid;
8990
ObjectAddress intoRelationAddr;
9091

9192
/* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
9293
is_matview = (into->viewQuery != NULL);
9394
relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION;
9495

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;
96+
/* Check if an existing materialized view needs to be replaced. */
97+
if (is_matview)
98+
{
99+
LOCKMODE lockmode;
109100

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);
101+
lockmode = into->replace ? AccessExclusiveLock : NoLock;
102+
(void) RangeVarGetAndCheckCreationNamespace(into->rel, lockmode,
103+
&matviewOid);
104+
replace = OidIsValid(matviewOid) && into->replace;
105+
}
115106

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();
107+
if (is_matview && replace)
108+
{
109+
Relation rel;
110+
List *atcmds = NIL;
111+
AlterTableCmd *atcmd;
112+
TupleDesc descriptor;
113+
114+
rel = relation_open(matviewOid, NoLock);
115+
116+
if (rel->rd_rel->relkind != RELKIND_MATVIEW)
117+
ereport(ERROR,
118+
errcode(ERRCODE_WRONG_OBJECT_TYPE),
119+
errmsg("\"%s\" is not a materialized view",
120+
RelationGetRelationName(rel)));
121+
122+
CheckTableNotInUse(rel, "CREATE OR REPLACE MATERIALIZED VIEW");
123+
124+
descriptor = BuildDescForRelation(attrList);
125+
checkViewColumns(descriptor, rel->rd_att, true);
126+
127+
/* add new attributes */
128+
if (list_length(attrList) > rel->rd_att->natts)
129+
{
130+
ListCell *c;
131+
int skip = rel->rd_att->natts;
132+
133+
foreach(c, attrList)
134+
{
135+
if (skip > 0)
136+
{
137+
skip--;
138+
continue;
139+
}
140+
atcmd = makeNode(AlterTableCmd);
141+
atcmd->subtype = AT_AddColumnToView;
142+
atcmd->def = (Node *) lfirst(c);
143+
atcmds = lappend(atcmds, atcmd);
144+
}
145+
}
146+
147+
/*
148+
* The following alters access method, tablespace, and storage options.
149+
* When replacing an existing matview we need to alter the relation
150+
* such that the defaults apply as if they have not been specified at
151+
* all by the CREATE statement.
152+
*/
153+
154+
/* access method */
155+
atcmd = makeNode(AlterTableCmd);
156+
atcmd->subtype = AT_SetAccessMethod;
157+
atcmd->name = into->accessMethod ? into->accessMethod : default_table_access_method;
158+
atcmds = lappend(atcmds, atcmd);
159+
160+
/* tablespace */
161+
atcmd = makeNode(AlterTableCmd);
162+
atcmd->subtype = AT_SetTableSpace;
163+
if (into->tableSpaceName != NULL)
164+
atcmd->name = into->tableSpaceName;
165+
else
166+
{
167+
Oid spcid;
168+
169+
/*
170+
* Resolve the name of the default or database tablespace because
171+
* we need to specify the tablespace by name.
172+
*
173+
* TODO: Move that to ATPrepSetTableSpace? Must allow AlterTableCmd.name to be NULL then.
174+
*/
175+
spcid = GetDefaultTablespace(RELPERSISTENCE_PERMANENT, false);
176+
if (!OidIsValid(spcid))
177+
spcid = MyDatabaseTableSpace;
178+
atcmd->name = get_tablespace_name(spcid);
179+
}
180+
atcmds = lappend(atcmds, atcmd);
181+
182+
/* storage options */
183+
atcmd = makeNode(AlterTableCmd);
184+
atcmd->subtype = AT_ReplaceRelOptions;
185+
atcmd->def = (Node *) into->options;
186+
atcmds = lappend(atcmds, atcmd);
187+
188+
AlterTableInternal(matviewOid, atcmds, true);
189+
CommandCounterIncrement();
190+
191+
relation_close(rel, NoLock);
192+
ObjectAddressSet(intoRelationAddr, RelationRelationId, matviewOid);
193+
}
194+
else
195+
{
196+
CreateStmt *create = makeNode(CreateStmt);
197+
Datum toast_options;
198+
const static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
199+
200+
/*
201+
* Create the target relation by faking up a CREATE TABLE parsetree
202+
* and passing it to DefineRelation.
203+
*/
204+
create->relation = into->rel;
205+
create->tableElts = attrList;
206+
create->inhRelations = NIL;
207+
create->ofTypename = NULL;
208+
create->constraints = NIL;
209+
create->options = into->options;
210+
create->oncommit = into->onCommit;
211+
create->tablespacename = into->tableSpaceName;
212+
create->if_not_exists = false;
213+
create->accessMethod = into->accessMethod;
214+
215+
/*
216+
* Create the relation. (This will error out if there's an existing
217+
* view, so we don't need more code to complain if "replace" is
218+
* false.)
219+
*/
220+
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL,
221+
NULL);
122222

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);
223+
/*
224+
* If necessary, create a TOAST table for the target table. Note that
225+
* NewRelationCreateToastTable ends with CommandCounterIncrement(), so
226+
* that the TOAST table will be visible for insertion.
227+
*/
228+
CommandCounterIncrement();
229+
230+
/* parse and validate reloptions for the toast table */
231+
toast_options = transformRelOptions((Datum) 0,
232+
create->options,
233+
"toast",
234+
validnsps,
235+
true, false);
129236

130-
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
237+
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
131238

132-
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
239+
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
240+
}
133241

134242
/* Create the "view" part of a materialized view. */
135243
if (is_matview)
136244
{
137245
/* StoreViewQuery scribbles on tree, so make a copy */
138246
Query *query = copyObject(into->viewQuery);
139247

140-
StoreViewQuery(intoRelationAddr.objectId, query, false);
248+
StoreViewQuery(intoRelationAddr.objectId, query, replace);
141249
CommandCounterIncrement();
142250
}
143251

@@ -234,7 +342,26 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
234342

235343
/* Check if the relation exists or not */
236344
if (CreateTableAsRelExists(stmt))
345+
{
346+
/* An existing materialized view can be replaced. */
347+
if (is_matview && into->replace)
348+
{
349+
RefreshMatViewStmt *refresh;
350+
351+
/* Change the relation to match the new query and other options. */
352+
(void) create_ctas_nodata(query->targetList, into);
353+
354+
/* Refresh the materialized view with a fake statement. */
355+
refresh = makeNode(RefreshMatViewStmt);
356+
refresh->relation = into->rel;
357+
refresh->skipData = into->skipData;
358+
refresh->concurrent = false;
359+
360+
return ExecRefreshMatView(refresh, pstate->p_sourcetext, qc);
361+
}
362+
237363
return InvalidObjectAddress;
364+
}
238365

239366
/*
240367
* Create the tuple receiver object and insert info it will need
@@ -402,26 +529,28 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas)
402529
oldrelid = get_relname_relid(into->rel->relname, nspid);
403530
if (OidIsValid(oldrelid))
404531
{
405-
if (!ctas->if_not_exists)
532+
if (!ctas->if_not_exists && !into->replace)
406533
ereport(ERROR,
407534
(errcode(ERRCODE_DUPLICATE_TABLE),
408535
errmsg("relation \"%s\" already exists",
409536
into->rel->relname)));
410537

411538
/*
412-
* The relation exists and IF NOT EXISTS has been specified.
539+
* The relation exists and IF NOT EXISTS or OR REPLACE has been
540+
* specified.
413541
*
414542
* If we are in an extension script, insist that the pre-existing
415543
* object be a member of the extension, to avoid security risks.
416544
*/
417545
ObjectAddressSet(address, RelationRelationId, oldrelid);
418546
checkMembershipInCurrentExtension(&address);
419547

420-
/* OK to skip */
421-
ereport(NOTICE,
422-
(errcode(ERRCODE_DUPLICATE_TABLE),
423-
errmsg("relation \"%s\" already exists, skipping",
424-
into->rel->relname)));
548+
if (ctas->if_not_exists)
549+
/* OK to skip */
550+
ereport(NOTICE,
551+
(errcode(ERRCODE_DUPLICATE_TABLE),
552+
errmsg("relation \"%s\" already exists, skipping",
553+
into->rel->relname)));
425554
return true;
426555
}
427556

src/backend/commands/tablecmds.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4648,7 +4648,7 @@ AlterTableGetLockLevel(List *cmds)
46484648
* Subcommands that may be visible to concurrent SELECTs
46494649
*/
46504650
case AT_DropColumn: /* change visible to SELECT */
4651-
case AT_AddColumnToView: /* CREATE VIEW */
4651+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
46524652
case AT_DropOids: /* used to equiv to DropColumn */
46534653
case AT_EnableAlwaysRule: /* may change SELECT rules */
46544654
case AT_EnableReplicaRule: /* may change SELECT rules */
@@ -4943,8 +4943,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
49434943
/* Recursion occurs during execution phase */
49444944
pass = AT_PASS_ADD_COL;
49454945
break;
4946-
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
4947-
ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
4946+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
4947+
ATSimplePermissions(cmd->subtype, rel, ATT_VIEW | ATT_MATVIEW);
49484948
ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
49494949
lockmode, context);
49504950
/* Recursion occurs during execution phase */
@@ -5375,7 +5375,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
53755375
switch (cmd->subtype)
53765376
{
53775377
case AT_AddColumn: /* ADD COLUMN */
5378-
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5378+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
53795379
address = ATExecAddColumn(wqueue, tab, rel, &cmd,
53805380
cmd->recurse, false,
53815381
lockmode, cur_pass, context);

0 commit comments

Comments
 (0)