Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f473c6f

Browse files
author
Commitfest Bot
committed
[CF 5099] v6 - CREATE OR REPLACE 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/bf64dad9-eaaa-4911-b447-13acee89ea80@ewie.name Author(s): Erik Wienhold
2 parents a7187c3 + c671a8e commit f473c6f

File tree

12 files changed

+749
-96
lines changed

12 files changed

+749
-96
lines changed

doc/src/sgml/ref/create_materialized_view.sgml

Lines changed: 26 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,23 @@ 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. For
178+
newly created materialized views, this has the same effect as
179+
<command>WITH DATA</command>. Use this form if you want to use
180+
<command>REFRESH MATERIALIZED VIEW CONCURRENTLY</command> as it requires
181+
a populated materialized view.
182+
</para>
162183
</listitem>
163184
</varlistentry>
164185

src/backend/commands/createas.c

Lines changed: 170 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -81,63 +81,159 @@ 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)));
122119

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);
120+
CheckTableNotInUse(rel, "CREATE OR REPLACE MATERIALIZED VIEW");
129121

130-
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
122+
descriptor = BuildDescForRelation(attrList);
123+
checkViewColumns(descriptor, rel->rd_att, true);
131124

132-
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
125+
/* Add new attributes via ALTER TABLE. */
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+
/* Set access method via ALTER TABLE. */
146+
if (into->accessMethod != NULL)
147+
{
148+
atcmd = makeNode(AlterTableCmd);
149+
atcmd->subtype = AT_SetAccessMethod;
150+
atcmd->name = into->accessMethod;
151+
atcmds = lappend(atcmds, atcmd);
152+
}
153+
154+
/* Set tablespace via ALTER TABLE. */
155+
if (into->tableSpaceName != NULL)
156+
{
157+
atcmd = makeNode(AlterTableCmd);
158+
atcmd->subtype = AT_SetTableSpace;
159+
atcmd->name = into->tableSpaceName;
160+
atcmds = lappend(atcmds, atcmd);
161+
}
162+
163+
/* Set storage parameters via ALTER TABLE. */
164+
if (into->options != NIL)
165+
{
166+
atcmd = makeNode(AlterTableCmd);
167+
atcmd->subtype = AT_ReplaceRelOptions;
168+
atcmd->def = (Node *) into->options;
169+
atcmds = lappend(atcmds, atcmd);
170+
}
171+
172+
if (atcmds != NIL)
173+
{
174+
AlterTableInternal(matviewOid, atcmds, true);
175+
CommandCounterIncrement();
176+
}
177+
178+
relation_close(rel, NoLock);
179+
180+
ObjectAddressSet(intoRelationAddr, RelationRelationId, matviewOid);
181+
}
182+
else
183+
{
184+
CreateStmt *create = makeNode(CreateStmt);
185+
Datum toast_options;
186+
const static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
187+
188+
/*
189+
* Create the target relation by faking up a CREATE TABLE parsetree
190+
* and passing it to DefineRelation.
191+
*/
192+
create->relation = into->rel;
193+
create->tableElts = attrList;
194+
create->inhRelations = NIL;
195+
create->ofTypename = NULL;
196+
create->constraints = NIL;
197+
create->options = into->options;
198+
create->oncommit = into->onCommit;
199+
create->tablespacename = into->tableSpaceName;
200+
create->if_not_exists = false;
201+
create->accessMethod = into->accessMethod;
202+
203+
/*
204+
* Create the relation. (This will error out if there's an existing
205+
* view, so we don't need more code to complain if "replace" is
206+
* false.)
207+
*/
208+
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL,
209+
NULL);
210+
211+
/*
212+
* If necessary, create a TOAST table for the target table. Note that
213+
* NewRelationCreateToastTable ends with CommandCounterIncrement(), so
214+
* that the TOAST table will be visible for insertion.
215+
*/
216+
CommandCounterIncrement();
217+
218+
/* parse and validate reloptions for the toast table */
219+
toast_options = transformRelOptions((Datum) 0,
220+
create->options,
221+
"toast",
222+
validnsps,
223+
true, false);
224+
225+
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
226+
227+
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
228+
}
133229

134230
/* Create the "view" part of a materialized view. */
135231
if (is_matview)
136232
{
137233
/* StoreViewQuery scribbles on tree, so make a copy */
138234
Query *query = copyObject(into->viewQuery);
139235

140-
StoreViewQuery(intoRelationAddr.objectId, query, false);
236+
StoreViewQuery(intoRelationAddr.objectId, query, replace);
141237
CommandCounterIncrement();
142238
}
143239

@@ -234,7 +330,34 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
234330

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

239362
/*
240363
* Create the tuple receiver object and insert info it will need
@@ -403,26 +526,28 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas)
403526
oldrelid = get_relname_relid(into->rel->relname, nspid);
404527
if (OidIsValid(oldrelid))
405528
{
406-
if (!ctas->if_not_exists)
529+
if (!ctas->if_not_exists && !into->replace)
407530
ereport(ERROR,
408531
(errcode(ERRCODE_DUPLICATE_TABLE),
409532
errmsg("relation \"%s\" already exists",
410533
into->rel->relname)));
411534

412535
/*
413-
* The relation exists and IF NOT EXISTS has been specified.
536+
* The relation exists and IF NOT EXISTS or OR REPLACE has been
537+
* specified.
414538
*
415539
* If we are in an extension script, insist that the pre-existing
416540
* object be a member of the extension, to avoid security risks.
417541
*/
418542
ObjectAddressSet(address, RelationRelationId, oldrelid);
419543
checkMembershipInCurrentExtension(&address);
420544

421-
/* OK to skip */
422-
ereport(NOTICE,
423-
(errcode(ERRCODE_DUPLICATE_TABLE),
424-
errmsg("relation \"%s\" already exists, skipping",
425-
into->rel->relname)));
545+
if (ctas->if_not_exists)
546+
/* OK to skip */
547+
ereport(NOTICE,
548+
(errcode(ERRCODE_DUPLICATE_TABLE),
549+
errmsg("relation \"%s\" already exists, skipping",
550+
into->rel->relname)));
426551
return true;
427552
}
428553

src/backend/commands/tablecmds.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4642,7 +4642,7 @@ AlterTableGetLockLevel(List *cmds)
46424642
* Subcommands that may be visible to concurrent SELECTs
46434643
*/
46444644
case AT_DropColumn: /* change visible to SELECT */
4645-
case AT_AddColumnToView: /* CREATE VIEW */
4645+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
46464646
case AT_DropOids: /* used to equiv to DropColumn */
46474647
case AT_EnableAlwaysRule: /* may change SELECT rules */
46484648
case AT_EnableReplicaRule: /* may change SELECT rules */
@@ -4937,8 +4937,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
49374937
/* Recursion occurs during execution phase */
49384938
pass = AT_PASS_ADD_COL;
49394939
break;
4940-
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
4941-
ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
4940+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
4941+
ATSimplePermissions(cmd->subtype, rel, ATT_VIEW | ATT_MATVIEW);
49424942
ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
49434943
lockmode, context);
49444944
/* Recursion occurs during execution phase */
@@ -5369,7 +5369,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
53695369
switch (cmd->subtype)
53705370
{
53715371
case AT_AddColumn: /* ADD COLUMN */
5372-
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5372+
case AT_AddColumnToView: /* via CREATE OR REPLACE [MATERIALIZED] VIEW */
53735373
address = ATExecAddColumn(wqueue, tab, rel, &cmd,
53745374
cmd->recurse, false,
53755375
lockmode, cur_pass, context);

0 commit comments

Comments
 (0)