|
24 | 24 | */ |
25 | 25 | #include "postgres.h" |
26 | 26 |
|
| 27 | +#include "miscadmin.h" |
27 | 28 | #include "access/heapam.h" |
28 | 29 | #include "access/reloptions.h" |
29 | 30 | #include "access/tableam.h" |
|
34 | 35 | #include "commands/matview.h" |
35 | 36 | #include "commands/prepare.h" |
36 | 37 | #include "commands/tablecmds.h" |
| 38 | +#include "commands/tablespace.h" |
37 | 39 | #include "commands/view.h" |
38 | 40 | #include "executor/execdesc.h" |
39 | 41 | #include "executor/executor.h" |
@@ -81,63 +83,169 @@ static void intorel_destroy(DestReceiver *self); |
81 | 83 | static ObjectAddress |
82 | 84 | create_ctas_internal(List *attrList, IntoClause *into) |
83 | 85 | { |
84 | | - CreateStmt *create = makeNode(CreateStmt); |
85 | | - bool is_matview; |
| 86 | + bool is_matview, |
| 87 | + replace = false; |
86 | 88 | char relkind; |
87 | | - Datum toast_options; |
88 | | - const char *const validnsps[] = HEAP_RELOPT_NAMESPACES; |
| 89 | + Oid matviewOid = InvalidOid; |
89 | 90 | ObjectAddress intoRelationAddr; |
90 | 91 |
|
91 | 92 | /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ |
92 | 93 | is_matview = (into->viewQuery != NULL); |
93 | 94 | relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; |
94 | 95 |
|
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; |
109 | 100 |
|
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 | + } |
115 | 106 |
|
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); |
122 | 222 |
|
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); |
129 | 236 |
|
130 | | - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); |
| 237 | + (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); |
131 | 238 |
|
132 | | - NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); |
| 239 | + NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); |
| 240 | + } |
133 | 241 |
|
134 | 242 | /* Create the "view" part of a materialized view. */ |
135 | 243 | if (is_matview) |
136 | 244 | { |
137 | 245 | /* StoreViewQuery scribbles on tree, so make a copy */ |
138 | 246 | Query *query = copyObject(into->viewQuery); |
139 | 247 |
|
140 | | - StoreViewQuery(intoRelationAddr.objectId, query, false); |
| 248 | + StoreViewQuery(intoRelationAddr.objectId, query, replace); |
141 | 249 | CommandCounterIncrement(); |
142 | 250 | } |
143 | 251 |
|
@@ -234,7 +342,26 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, |
234 | 342 |
|
235 | 343 | /* Check if the relation exists or not */ |
236 | 344 | 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 | + |
237 | 363 | return InvalidObjectAddress; |
| 364 | + } |
238 | 365 |
|
239 | 366 | /* |
240 | 367 | * Create the tuple receiver object and insert info it will need |
@@ -402,26 +529,28 @@ CreateTableAsRelExists(CreateTableAsStmt *ctas) |
402 | 529 | oldrelid = get_relname_relid(into->rel->relname, nspid); |
403 | 530 | if (OidIsValid(oldrelid)) |
404 | 531 | { |
405 | | - if (!ctas->if_not_exists) |
| 532 | + if (!ctas->if_not_exists && !into->replace) |
406 | 533 | ereport(ERROR, |
407 | 534 | (errcode(ERRCODE_DUPLICATE_TABLE), |
408 | 535 | errmsg("relation \"%s\" already exists", |
409 | 536 | into->rel->relname))); |
410 | 537 |
|
411 | 538 | /* |
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. |
413 | 541 | * |
414 | 542 | * If we are in an extension script, insist that the pre-existing |
415 | 543 | * object be a member of the extension, to avoid security risks. |
416 | 544 | */ |
417 | 545 | ObjectAddressSet(address, RelationRelationId, oldrelid); |
418 | 546 | checkMembershipInCurrentExtension(&address); |
419 | 547 |
|
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))); |
425 | 554 | return true; |
426 | 555 | } |
427 | 556 |
|
|
0 commit comments