Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * createas.c
4 : * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO.
5 : * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors,
6 : * we implement that here, too.
7 : *
8 : * We implement this by diverting the query's normal output to a
9 : * specialized DestReceiver type.
10 : *
11 : * Formerly, CTAS was implemented as a variant of SELECT, which led
12 : * to assorted legacy behaviors that we still try to preserve, notably that
13 : * we must return a tuples-processed count in the QueryCompletion. (We no
14 : * longer do that for CTAS ... WITH NO DATA, however.)
15 : *
16 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
17 : * Portions Copyright (c) 1994, Regents of the University of California
18 : *
19 : *
20 : * IDENTIFICATION
21 : * src/backend/commands/createas.c
22 : *
23 : *-------------------------------------------------------------------------
24 : */
25 : #include "postgres.h"
26 :
27 : #include "access/heapam.h"
28 : #include "access/htup_details.h"
29 : #include "access/reloptions.h"
30 : #include "access/sysattr.h"
31 : #include "access/tableam.h"
32 : #include "access/xact.h"
33 : #include "access/xlog.h"
34 : #include "catalog/namespace.h"
35 : #include "catalog/toasting.h"
36 : #include "commands/createas.h"
37 : #include "commands/matview.h"
38 : #include "commands/prepare.h"
39 : #include "commands/tablecmds.h"
40 : #include "commands/view.h"
41 : #include "miscadmin.h"
42 : #include "nodes/makefuncs.h"
43 : #include "nodes/nodeFuncs.h"
44 : #include "parser/parse_clause.h"
45 : #include "rewrite/rewriteHandler.h"
46 : #include "storage/smgr.h"
47 : #include "tcop/tcopprot.h"
48 : #include "utils/builtins.h"
49 : #include "utils/lsyscache.h"
50 : #include "utils/rel.h"
51 : #include "utils/rls.h"
52 : #include "utils/snapmgr.h"
53 :
54 : typedef struct
55 : {
56 : DestReceiver pub; /* publicly-known function pointers */
57 : IntoClause *into; /* target relation specification */
58 : /* These fields are filled by intorel_startup: */
59 : Relation rel; /* relation to write to */
60 : ObjectAddress reladdr; /* address of rel, for ExecCreateTableAs */
61 : CommandId output_cid; /* cmin to insert in output tuples */
62 : int ti_options; /* table_tuple_insert performance options */
63 : BulkInsertState bistate; /* bulk insert state */
64 : } DR_intorel;
65 :
66 : /* utility functions for CTAS definition creation */
67 : static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
68 : static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
69 :
70 : /* DestReceiver routines for collecting data */
71 : static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
72 : static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
73 : static void intorel_shutdown(DestReceiver *self);
74 : static void intorel_destroy(DestReceiver *self);
75 :
76 :
77 : /*
78 : * create_ctas_internal
79 : *
80 : * Internal utility used for the creation of the definition of a relation
81 : * created via CREATE TABLE AS or a materialized view. Caller needs to
82 : * provide a list of attributes (ColumnDef nodes).
83 : */
84 : static ObjectAddress
2477 tgl 85 CBC 801 : create_ctas_internal(List *attrList, IntoClause *into)
86 : {
87 801 : CreateStmt *create = makeNode(CreateStmt);
88 : bool is_matview;
89 : char relkind;
90 : Datum toast_options;
91 : static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
92 : ObjectAddress intoRelationAddr;
93 :
94 : /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
95 801 : is_matview = (into->viewQuery != NULL);
96 801 : relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION;
97 :
98 : /*
99 : * Create the target relation by faking up a CREATE TABLE parsetree and
100 : * passing it to DefineRelation.
101 : */
102 801 : create->relation = into->rel;
103 801 : create->tableElts = attrList;
104 801 : create->inhRelations = NIL;
105 801 : create->ofTypename = NULL;
106 801 : create->constraints = NIL;
107 801 : create->options = into->options;
108 801 : create->oncommit = into->onCommit;
109 801 : create->tablespacename = into->tableSpaceName;
110 801 : create->if_not_exists = false;
1495 andres 111 801 : create->accessMethod = into->accessMethod;
112 :
113 : /*
114 : * Create the relation. (This will error out if there's an existing view,
115 : * so we don't need more code to complain if "replace" is false.)
116 : */
2314 rhaas 117 801 : intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
118 :
119 : /*
120 : * If necessary, create a TOAST table for the target table. Note that
121 : * NewRelationCreateToastTable ends with CommandCounterIncrement(), so
122 : * that the TOAST table will be visible for insertion.
123 : */
2477 tgl 124 792 : CommandCounterIncrement();
125 :
126 : /* parse and validate reloptions for the toast table */
127 792 : toast_options = transformRelOptions((Datum) 0,
128 : create->options,
129 : "toast",
130 : validnsps,
131 : true, false);
132 :
133 792 : (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
134 :
135 792 : NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
136 :
137 : /* Create the "view" part of a materialized view. */
138 792 : if (is_matview)
139 : {
140 : /* StoreViewQuery scribbles on tree, so make a copy */
141 212 : Query *query = (Query *) copyObject(into->viewQuery);
142 :
143 212 : StoreViewQuery(intoRelationAddr.objectId, query, false);
144 212 : CommandCounterIncrement();
145 : }
146 :
147 792 : return intoRelationAddr;
148 : }
149 :
150 :
151 : /*
152 : * create_ctas_nodata
153 : *
154 : * Create CTAS or materialized view when WITH NO DATA is used, starting from
155 : * the targetlist of the SELECT or view definition.
156 : */
157 : static ObjectAddress
158 54 : create_ctas_nodata(List *tlist, IntoClause *into)
159 : {
160 : List *attrList;
161 : ListCell *t,
162 : *lc;
163 :
164 : /*
165 : * Build list of ColumnDefs from non-junk elements of the tlist. If a
166 : * column name list was specified in CREATE TABLE AS, override the column
167 : * names in the query. (Too few column names are OK, too many are not.)
168 : */
169 54 : attrList = NIL;
170 54 : lc = list_head(into->colNames);
171 174 : foreach(t, tlist)
172 : {
173 120 : TargetEntry *tle = (TargetEntry *) lfirst(t);
174 :
175 120 : if (!tle->resjunk)
176 : {
177 : ColumnDef *col;
178 : char *colname;
179 :
180 120 : if (lc)
181 : {
182 38 : colname = strVal(lfirst(lc));
1364 183 38 : lc = lnext(into->colNames, lc);
184 : }
185 : else
2477 186 82 : colname = tle->resname;
187 :
188 120 : col = makeColumnDef(colname,
189 120 : exprType((Node *) tle->expr),
190 120 : exprTypmod((Node *) tle->expr),
191 120 : exprCollation((Node *) tle->expr));
192 :
193 : /*
194 : * It's possible that the column is of a collatable type but the
195 : * collation could not be resolved, so double-check. (We must
196 : * check this here because DefineRelation would adopt the type's
197 : * default collation rather than complaining.)
198 : */
199 230 : if (!OidIsValid(col->collOid) &&
200 110 : type_is_collatable(col->typeName->typeOid))
2477 tgl 201 UBC 0 : ereport(ERROR,
202 : (errcode(ERRCODE_INDETERMINATE_COLLATION),
203 : errmsg("no collation was derived for column \"%s\" with collatable type %s",
204 : col->colname,
205 : format_type_be(col->typeName->typeOid)),
206 : errhint("Use the COLLATE clause to set the collation explicitly.")));
207 :
2477 tgl 208 CBC 120 : attrList = lappend(attrList, col);
209 : }
210 : }
211 :
212 54 : if (lc != NULL)
213 6 : ereport(ERROR,
214 : (errcode(ERRCODE_SYNTAX_ERROR),
215 : errmsg("too many column names were specified")));
216 :
217 : /* Create the relation definition using the ColumnDef list */
218 48 : return create_ctas_internal(attrList, into);
219 : }
220 :
221 :
222 : /*
223 : * ExecCreateTableAs -- execute a CREATE TABLE AS command
224 : */
225 : ObjectAddress
1191 peter 226 847 : ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
227 : ParamListInfo params, QueryEnvironment *queryEnv,
228 : QueryCompletion *qc)
229 : {
2264 andres 230 847 : Query *query = castNode(Query, stmt->query);
4038 tgl 231 847 : IntoClause *into = stmt->into;
3558 noah 232 847 : bool is_matview = (into->viewQuery != NULL);
233 : DestReceiver *dest;
234 847 : Oid save_userid = InvalidOid;
235 847 : int save_sec_context = 0;
236 847 : int save_nestlevel = 0;
237 : ObjectAddress address;
238 : List *rewritten;
239 : PlannedStmt *plan;
240 : QueryDesc *queryDesc;
241 :
242 : /* Check if the relation exists or not */
830 michael 243 847 : if (CreateTableAsRelExists(stmt))
244 23 : return InvalidObjectAddress;
245 :
246 : /*
247 : * Create the tuple receiver object and insert info it will need
248 : */
4038 tgl 249 801 : dest = CreateIntoRelDestReceiver(into);
250 :
251 : /*
252 : * The contained Query could be a SELECT, or an EXECUTE utility command.
253 : * If the latter, we just pass it off to ExecuteQuery.
254 : */
255 801 : if (query->commandType == CMD_UTILITY &&
256 21 : IsA(query->utilityStmt, ExecuteStmt))
257 : {
2264 andres 258 21 : ExecuteStmt *estmt = castNode(ExecuteStmt, query->utilityStmt);
259 :
3558 noah 260 21 : Assert(!is_matview); /* excluded by syntax */
1133 alvherre 261 21 : ExecuteQuery(pstate, estmt, into, params, dest, qc);
262 :
263 : /* get object address that intorel_startup saved for us */
2477 tgl 264 21 : address = ((DR_intorel *) dest)->reladdr;
265 :
2959 alvherre 266 21 : return address;
267 : }
3649 tgl 268 780 : Assert(query->commandType == CMD_SELECT);
269 :
270 : /*
271 : * For materialized views, lock down security-restricted operations and
272 : * arrange to make GUC variable changes local to this command. This is
273 : * not necessary for security, but this keeps the behavior similar to
274 : * REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized
275 : * view not possible to refresh.
276 : */
3558 noah 277 780 : if (is_matview)
278 : {
279 215 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
280 215 : SetUserIdAndSecContext(save_userid,
281 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
282 215 : save_nestlevel = NewGUCNestLevel();
283 : }
284 :
2477 tgl 285 780 : if (into->skipData)
286 : {
287 : /*
288 : * If WITH NO DATA was specified, do not go through the rewriter,
289 : * planner and executor. Just define the relation using a code path
290 : * similar to CREATE VIEW. This avoids dump/restore problems stemming
291 : * from running the planner before all dependencies are set up.
292 : */
293 54 : address = create_ctas_nodata(query->targetList, into);
294 : }
295 : else
296 : {
297 : /*
298 : * Parse analysis was done already, but we still have to run the rule
299 : * rewriter. We do not do AcquireRewriteLocks: we assume the query
300 : * either came straight from the parser, or suitable locks were
301 : * acquired by plancache.c.
302 : */
660 303 726 : rewritten = QueryRewrite(query);
304 :
305 : /* SELECT should never rewrite to more or less than one SELECT query */
2477 306 726 : if (list_length(rewritten) != 1)
2477 tgl 307 UBC 0 : elog(ERROR, "unexpected rewrite result for %s",
308 : is_matview ? "CREATE MATERIALIZED VIEW" :
309 : "CREATE TABLE AS SELECT");
2190 tgl 310 CBC 726 : query = linitial_node(Query, rewritten);
2477 311 726 : Assert(query->commandType == CMD_SELECT);
312 :
313 : /* plan the query */
1105 fujii 314 726 : plan = pg_plan_query(query, pstate->p_sourcetext,
315 : CURSOR_OPT_PARALLEL_OK, params);
316 :
317 : /*
318 : * Use a snapshot with an updated command ID to ensure this query sees
319 : * results of any previously executed queries. (This could only
320 : * matter if the planner executed an allegedly-stable function that
321 : * changed the database contents, but let's do it anyway to be
322 : * parallel to the EXPLAIN code path.)
323 : */
2477 tgl 324 723 : PushCopiedSnapshot(GetActiveSnapshot());
325 723 : UpdateActiveSnapshotCommandId();
326 :
327 : /* Create a QueryDesc, redirecting output to our tuple receiver */
1191 peter 328 723 : queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
329 : GetActiveSnapshot(), InvalidSnapshot,
330 : dest, params, queryEnv, 0);
331 :
332 : /* call ExecutorStart to prepare the plan for execution */
2477 tgl 333 723 : ExecutorStart(queryDesc, GetIntoRelEFlags(into));
334 :
335 : /* run the plan to completion */
11 peter 336 GNC 723 : ExecutorRun(queryDesc, ForwardScanDirection, 0, true);
337 :
338 : /* save the rowcount if we're given a qc to fill */
1133 alvherre 339 CBC 699 : if (qc)
340 689 : SetQueryCompletion(qc, CMDTAG_SELECT, queryDesc->estate->es_processed);
341 :
342 : /* get object address that intorel_startup saved for us */
2477 tgl 343 699 : address = ((DR_intorel *) dest)->reladdr;
344 :
345 : /* and clean up */
346 699 : ExecutorFinish(queryDesc);
347 699 : ExecutorEnd(queryDesc);
348 :
349 699 : FreeQueryDesc(queryDesc);
350 :
351 699 : PopActiveSnapshot();
352 : }
353 :
3558 noah 354 747 : if (is_matview)
355 : {
356 : /* Roll back any GUC changes */
357 206 : AtEOXact_GUC(false, save_nestlevel);
358 :
359 : /* Restore userid and security context */
360 206 : SetUserIdAndSecContext(save_userid, save_sec_context);
361 : }
362 :
2959 alvherre 363 747 : return address;
364 : }
365 :
366 : /*
367 : * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
368 : *
369 : * This is exported because EXPLAIN and PREPARE need it too. (Note: those
370 : * callers still need to deal explicitly with the skipData flag; since they
371 : * use different methods for suppressing execution, it doesn't seem worth
372 : * trying to encapsulate that part.)
373 : */
374 : int
4038 tgl 375 789 : GetIntoRelEFlags(IntoClause *intoClause)
376 : {
1601 andres 377 789 : int flags = 0;
378 :
3689 kgrittn 379 789 : if (intoClause->skipData)
380 21 : flags |= EXEC_FLAG_WITH_NO_DATA;
381 :
382 789 : return flags;
383 : }
384 :
385 : /*
386 : * CreateTableAsRelExists --- check existence of relation for CreateTableAsStmt
387 : *
388 : * Utility wrapper checking if the relation pending for creation in this
389 : * CreateTableAsStmt query already exists or not. Returns true if the
390 : * relation exists, otherwise false.
391 : */
392 : bool
830 michael 393 922 : CreateTableAsRelExists(CreateTableAsStmt *ctas)
394 : {
395 : Oid nspid;
396 : Oid oldrelid;
397 : ObjectAddress address;
398 922 : IntoClause *into = ctas->into;
399 :
400 922 : nspid = RangeVarGetCreationNamespace(into->rel);
401 :
244 tgl 402 922 : oldrelid = get_relname_relid(into->rel->relname, nspid);
403 922 : if (OidIsValid(oldrelid))
404 : {
830 michael 405 76 : if (!ctas->if_not_exists)
406 36 : ereport(ERROR,
407 : (errcode(ERRCODE_DUPLICATE_TABLE),
408 : errmsg("relation \"%s\" already exists",
409 : into->rel->relname)));
410 :
411 : /*
412 : * The relation exists and IF NOT EXISTS has been specified.
413 : *
414 : * If we are in an extension script, insist that the pre-existing
415 : * object be a member of the extension, to avoid security risks.
416 : */
244 tgl 417 40 : ObjectAddressSet(address, RelationRelationId, oldrelid);
418 40 : checkMembershipInCurrentExtension(&address);
419 :
420 : /* OK to skip */
830 michael 421 38 : ereport(NOTICE,
422 : (errcode(ERRCODE_DUPLICATE_TABLE),
423 : errmsg("relation \"%s\" already exists, skipping",
424 : into->rel->relname)));
425 38 : return true;
426 : }
427 :
428 : /* Relation does not exist, it can be created */
429 846 : return false;
430 : }
431 :
432 : /*
433 : * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
434 : *
435 : * intoClause will be NULL if called from CreateDestReceiver(), in which
436 : * case it has to be provided later. However, it is convenient to allow
437 : * self->into to be filled in immediately for other callers.
438 : */
439 : DestReceiver *
4038 tgl 440 846 : CreateIntoRelDestReceiver(IntoClause *intoClause)
441 : {
442 846 : DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
443 :
444 846 : self->pub.receiveSlot = intorel_receive;
445 846 : self->pub.rStartup = intorel_startup;
446 846 : self->pub.rShutdown = intorel_shutdown;
447 846 : self->pub.rDestroy = intorel_destroy;
448 846 : self->pub.mydest = DestIntoRel;
449 846 : self->into = intoClause;
450 : /* other private fields will be set during intorel_startup */
451 :
452 846 : return (DestReceiver *) self;
453 : }
454 :
455 : /*
456 : * intorel_startup --- executor startup
457 : */
458 : static void
459 768 : intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
460 : {
461 768 : DR_intorel *myState = (DR_intorel *) self;
462 768 : IntoClause *into = myState->into;
463 : bool is_matview;
464 : List *attrList;
465 : ObjectAddress intoRelationAddr;
466 : Relation intoRelationDesc;
467 : ListCell *lc;
468 : int attnum;
469 :
470 768 : Assert(into != NULL); /* else somebody forgot to set it */
471 :
472 : /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
3649 473 768 : is_matview = (into->viewQuery != NULL);
474 :
475 : /*
476 : * Build column definitions using "pre-cooked" type and collation info. If
477 : * a column name list was specified in CREATE TABLE AS, override the
478 : * column names derived from the query. (Too few column names are OK, too
479 : * many are not.)
480 : */
2477 481 768 : attrList = NIL;
4038 482 768 : lc = list_head(into->colNames);
483 2972 : for (attnum = 0; attnum < typeinfo->natts; attnum++)
484 : {
2058 andres 485 2213 : Form_pg_attribute attribute = TupleDescAttr(typeinfo, attnum);
486 : ColumnDef *col;
487 : char *colname;
488 :
4038 tgl 489 2213 : if (lc)
490 : {
2477 491 127 : colname = strVal(lfirst(lc));
1364 492 127 : lc = lnext(into->colNames, lc);
493 : }
494 : else
2477 495 2086 : colname = NameStr(attribute->attname);
496 :
497 2213 : col = makeColumnDef(colname,
498 : attribute->atttypid,
499 : attribute->atttypmod,
500 : attribute->attcollation);
501 :
502 : /*
503 : * It's possible that the column is of a collatable type but the
504 : * collation could not be resolved, so double-check. (We must check
505 : * this here because DefineRelation would adopt the type's default
506 : * collation rather than complaining.)
507 : */
4038 508 4092 : if (!OidIsValid(col->collOid) &&
2477 509 1879 : type_is_collatable(col->typeName->typeOid))
4038 510 9 : ereport(ERROR,
511 : (errcode(ERRCODE_INDETERMINATE_COLLATION),
512 : errmsg("no collation was derived for column \"%s\" with collatable type %s",
513 : col->colname,
514 : format_type_be(col->typeName->typeOid)),
515 : errhint("Use the COLLATE clause to set the collation explicitly.")));
516 :
2477 517 2204 : attrList = lappend(attrList, col);
518 : }
519 :
4038 520 759 : if (lc != NULL)
521 6 : ereport(ERROR,
522 : (errcode(ERRCODE_SYNTAX_ERROR),
523 : errmsg("too many column names were specified")));
524 :
525 : /*
526 : * Actually create the target table
527 : */
2477 528 753 : intoRelationAddr = create_ctas_internal(attrList, into);
529 :
530 : /*
531 : * Finally we can open the target table
532 : */
1539 andres 533 744 : intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
534 :
535 : /*
536 : * Make sure the constructed table does not have RLS enabled.
537 : *
538 : * check_enable_rls() will ereport(ERROR) itself if the user has requested
539 : * something invalid, and otherwise will return RLS_ENABLED if RLS should
540 : * be enabled here. We don't actually support that currently, so throw
541 : * our own ereport(ERROR) if that happens.
542 : */
2959 alvherre 543 744 : if (check_enable_rls(intoRelationAddr.objectId, InvalidOid, false) == RLS_ENABLED)
3124 sfrost 544 UBC 0 : ereport(ERROR,
545 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
546 : errmsg("policies not yet implemented for this command")));
547 :
548 : /*
549 : * Tentatively mark the target as populated, if it's a matview and we're
550 : * going to fill it; otherwise, no change needed.
551 : */
3625 tgl 552 CBC 744 : if (is_matview && !into->skipData)
553 171 : SetMatViewPopulatedState(intoRelationDesc, true);
554 :
555 : /*
556 : * Fill private fields of myState for use by later routines
557 : */
4038 558 744 : myState->rel = intoRelationDesc;
2477 559 744 : myState->reladdr = intoRelationAddr;
4038 560 744 : myState->output_cid = GetCurrentCommandId(true);
1100 noah 561 744 : myState->ti_options = TABLE_INSERT_SKIP_FSM;
562 :
563 : /*
564 : * If WITH NO DATA is specified, there is no need to set up the state for
565 : * bulk inserts as there are no tuples to insert.
566 : */
874 michael 567 744 : if (!into->skipData)
568 726 : myState->bistate = GetBulkInsertState();
569 : else
570 18 : myState->bistate = NULL;
571 :
572 : /*
573 : * Valid smgr_targblock implies something already wrote to the relation.
574 : * This may be harmless, but this function hasn't planned for it.
575 : */
4038 tgl 576 744 : Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
577 744 : }
578 :
579 : /*
580 : * intorel_receive --- receive one tuple
581 : */
582 : static bool
583 1054157 : intorel_receive(TupleTableSlot *slot, DestReceiver *self)
584 : {
585 1054157 : DR_intorel *myState = (DR_intorel *) self;
586 :
587 : /* Nothing to insert if WITH NO DATA is specified. */
874 michael 588 1054157 : if (!myState->into->skipData)
589 : {
590 : /*
591 : * Note that the input slot might not be of the type of the target
592 : * relation. That's supported by table_tuple_insert(), but slightly
593 : * less efficient than inserting with the right slot - but the
594 : * alternative would be to copy into a slot of the right type, which
595 : * would not be cheap either. This also doesn't allow accessing per-AM
596 : * data (say a tuple's xmin), but since we don't do that here...
597 : */
598 1054157 : table_tuple_insert(myState->rel,
599 : slot,
600 : myState->output_cid,
601 : myState->ti_options,
602 : myState->bistate);
603 : }
604 :
605 : /* We know this is a newly created relation, so there are no indexes */
606 :
2498 rhaas 607 1054157 : return true;
608 : }
609 :
610 : /*
611 : * intorel_shutdown --- executor end
612 : */
613 : static void
4038 tgl 614 744 : intorel_shutdown(DestReceiver *self)
615 : {
616 744 : DR_intorel *myState = (DR_intorel *) self;
874 michael 617 744 : IntoClause *into = myState->into;
618 :
619 744 : if (!into->skipData)
620 : {
621 726 : FreeBulkInsertState(myState->bistate);
622 726 : table_finish_bulk_insert(myState->rel, myState->ti_options);
623 : }
624 :
625 : /* close rel, but keep lock until commit */
1539 andres 626 744 : table_close(myState->rel, NoLock);
4038 tgl 627 744 : myState->rel = NULL;
628 744 : }
629 :
630 : /*
631 : * intorel_destroy --- release DestReceiver object
632 : */
633 : static void
4038 tgl 634 UBC 0 : intorel_destroy(DestReceiver *self)
635 : {
636 0 : pfree(self);
637 0 : }
|