include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
- collationcmds.o constraint.o conversioncmds.o copy.o \
+ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \
foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten);
- Assert(query->commandType == CMD_SELECT);
- Assert(query->utilityStmt == NULL);
- /* Query mustn't use INTO, either */
- if (query->intoClause)
+ /* The grammar allows SELECT INTO, but we don't support that */
+ if (query->utilityStmt != NULL &&
+ IsA(query->utilityStmt, CreateTableAsStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
+ Assert(query->commandType == CMD_SELECT);
+ Assert(query->utilityStmt == NULL);
+
/* plan the query */
plan = planner(query, 0, NULL);
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * createas.c
+ * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO
+ *
+ * We implement this by diverting the query's normal output to a
+ * specialized DestReceiver type.
+ *
+ * Formerly, this command was implemented as a variant of SELECT, which led
+ * to assorted legacy behaviors that we still try to preserve, notably that
+ * we must return a tuples-processed count in the completionTag.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/createas.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/reloptions.h"
+#include "access/sysattr.h"
+#include "access/xact.h"
+#include "catalog/toasting.h"
+#include "commands/createas.h"
+#include "commands/prepare.h"
+#include "commands/tablecmds.h"
+#include "parser/parse_clause.h"
+#include "rewrite/rewriteHandler.h"
+#include "storage/smgr.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+
+
+typedef struct
+{
+ DestReceiver pub; /* publicly-known function pointers */
+ IntoClause *into; /* target relation specification */
+ /* These fields are filled by intorel_startup: */
+ Relation rel; /* relation to write to */
+ CommandId output_cid; /* cmin to insert in output tuples */
+ int hi_options; /* heap_insert performance options */
+ BulkInsertState bistate; /* bulk insert state */
+} DR_intorel;
+
+static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
+static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
+static void intorel_shutdown(DestReceiver *self);
+static void intorel_destroy(DestReceiver *self);
+
+
+/*
+ * ExecCreateTableAs -- execute a CREATE TABLE AS command
+ */
+void
+ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+ ParamListInfo params, char *completionTag)
+{
+ Query *query = (Query *) stmt->query;
+ IntoClause *into = stmt->into;
+ DestReceiver *dest;
+ List *rewritten;
+ PlannedStmt *plan;
+ QueryDesc *queryDesc;
+ ScanDirection dir;
+
+ /*
+ * Create the tuple receiver object and insert info it will need
+ */
+ dest = CreateIntoRelDestReceiver(into);
+
+ /*
+ * The contained Query could be a SELECT, or an EXECUTE utility command.
+ * If the latter, we just pass it off to ExecuteQuery.
+ */
+ Assert(IsA(query, Query));
+ if (query->commandType == CMD_UTILITY &&
+ IsA(query->utilityStmt, ExecuteStmt))
+ {
+ ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt;
+
+ ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
+
+ return;
+ }
+ Assert(query->commandType == CMD_SELECT);
+
+ /*
+ * Parse analysis was done already, but we still have to run the rule
+ * rewriter. We do not do AcquireRewriteLocks: we assume the query either
+ * came straight from the parser, or suitable locks were acquired by
+ * plancache.c.
+ *
+ * Because the rewriter and planner tend to scribble on the input, we make
+ * a preliminary copy of the source querytree. This prevents problems in
+ * the case that CTAS is in a portal or plpgsql function and is executed
+ * repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
+ */
+ rewritten = QueryRewrite((Query *) copyObject(stmt->query));
+
+ /* SELECT should never rewrite to more or less than one SELECT query */
+ if (list_length(rewritten) != 1)
+ elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
+ query = (Query *) linitial(rewritten);
+ Assert(query->commandType == CMD_SELECT);
+
+ /* plan the query */
+ plan = pg_plan_query(query, 0, params);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees
+ * results of any previously executed queries. (This could only matter
+ * if the planner executed an allegedly-stable function that changed
+ * the database contents, but let's do it anyway to be parallel to the
+ * EXPLAIN code path.)
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ /* Create a QueryDesc, redirecting output to our tuple receiver */
+ queryDesc = CreateQueryDesc(plan, queryString,
+ GetActiveSnapshot(), InvalidSnapshot,
+ dest, params, 0);
+
+ /* call ExecutorStart to prepare the plan for execution */
+ ExecutorStart(queryDesc, GetIntoRelEFlags(into));
+
+ /*
+ * Normally, we run the plan to completion; but if skipData is specified,
+ * just do tuple receiver startup and shutdown.
+ */
+ if (into->skipData)
+ dir = NoMovementScanDirection;
+ else
+ dir = ForwardScanDirection;
+
+ /* run the plan */
+ ExecutorRun(queryDesc, dir, 0L);
+
+ /* save the rowcount if we're given a completionTag to fill */
+ if (completionTag)
+ snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+ "SELECT %u", queryDesc->estate->es_processed);
+
+ /* and clean up */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+}
+
+/*
+ * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
+ *
+ * This is exported because EXPLAIN and PREPARE need it too. (Note: those
+ * callers still need to deal explicitly with the skipData flag; since they
+ * use different methods for suppressing execution, it doesn't seem worth
+ * trying to encapsulate that part.)
+ */
+int
+GetIntoRelEFlags(IntoClause *intoClause)
+{
+ /*
+ * We need to tell the executor whether it has to produce OIDs or not,
+ * because it doesn't have enough information to do so itself (since we
+ * can't build the target relation until after ExecutorStart).
+ */
+ if (interpretOidsOption(intoClause->options))
+ return EXEC_FLAG_WITH_OIDS;
+ else
+ return EXEC_FLAG_WITHOUT_OIDS;
+}
+
+/*
+ * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
+ *
+ * intoClause will be NULL if called from CreateDestReceiver(), in which
+ * case it has to be provided later. However, it is convenient to allow
+ * self->into to be filled in immediately for other callers.
+ */
+DestReceiver *
+CreateIntoRelDestReceiver(IntoClause *intoClause)
+{
+ DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
+
+ self->pub.receiveSlot = intorel_receive;
+ self->pub.rStartup = intorel_startup;
+ self->pub.rShutdown = intorel_shutdown;
+ self->pub.rDestroy = intorel_destroy;
+ self->pub.mydest = DestIntoRel;
+ self->into = intoClause;
+ /* other private fields will be set during intorel_startup */
+
+ return (DestReceiver *) self;
+}
+
+/*
+ * intorel_startup --- executor startup
+ */
+static void
+intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ DR_intorel *myState = (DR_intorel *) self;
+ IntoClause *into = myState->into;
+ CreateStmt *create;
+ Oid intoRelationId;
+ Relation intoRelationDesc;
+ RangeTblEntry *rte;
+ Datum toast_options;
+ ListCell *lc;
+ int attnum;
+ static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+
+ Assert(into != NULL); /* else somebody forgot to set it */
+
+ /*
+ * Create the target relation by faking up a CREATE TABLE parsetree and
+ * passing it to DefineRelation.
+ */
+ create = makeNode(CreateStmt);
+ create->relation = into->rel;
+ create->tableElts = NIL; /* will fill below */
+ create->inhRelations = NIL;
+ create->ofTypename = NULL;
+ create->constraints = NIL;
+ create->options = into->options;
+ create->oncommit = into->onCommit;
+ create->tablespacename = into->tableSpaceName;
+ create->if_not_exists = false;
+
+ /*
+ * Build column definitions using "pre-cooked" type and collation info.
+ * If a column name list was specified in CREATE TABLE AS, override the
+ * column names derived from the query. (Too few column names are OK, too
+ * many are not.)
+ */
+ lc = list_head(into->colNames);
+ for (attnum = 0; attnum < typeinfo->natts; attnum++)
+ {
+ Form_pg_attribute attribute = typeinfo->attrs[attnum];
+ ColumnDef *col = makeNode(ColumnDef);
+ TypeName *coltype = makeNode(TypeName);
+
+ if (lc)
+ {
+ col->colname = strVal(lfirst(lc));
+ lc = lnext(lc);
+ }
+ else
+ col->colname = NameStr(attribute->attname);
+ col->typeName = coltype;
+ col->inhcount = 0;
+ col->is_local = true;
+ col->is_not_null = false;
+ col->is_from_type = false;
+ col->storage = 0;
+ col->raw_default = NULL;
+ col->cooked_default = NULL;
+ col->collClause = NULL;
+ col->collOid = attribute->attcollation;
+ col->constraints = NIL;
+ col->fdwoptions = NIL;
+
+ coltype->names = NIL;
+ coltype->typeOid = attribute->atttypid;
+ coltype->setof = false;
+ coltype->pct_type = false;
+ coltype->typmods = NIL;
+ coltype->typemod = attribute->atttypmod;
+ coltype->arrayBounds = NIL;
+ coltype->location = -1;
+
+ /*
+ * It's possible that the column is of a collatable type but the
+ * collation could not be resolved, so double-check. (We must
+ * check this here because DefineRelation would adopt the type's
+ * default collation rather than complaining.)
+ */
+ if (!OidIsValid(col->collOid) &&
+ type_is_collatable(coltype->typeOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_COLLATION),
+ errmsg("no collation was derived for column \"%s\" with collatable type %s",
+ col->colname, format_type_be(coltype->typeOid)),
+ errhint("Use the COLLATE clause to set the collation explicitly.")));
+
+ create->tableElts = lappend(create->tableElts, col);
+ }
+
+ if (lc != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("CREATE TABLE AS specifies too many column names")));
+
+ /*
+ * Actually create the target table
+ */
+ intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
+
+ /*
+ * If necessary, create a TOAST table for the target table. Note that
+ * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
+ * the TOAST table will be visible for insertion.
+ */
+ CommandCounterIncrement();
+
+ /* parse and validate reloptions for the toast table */
+ toast_options = transformRelOptions((Datum) 0,
+ create->options,
+ "toast",
+ validnsps,
+ true, false);
+
+ (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
+
+ AlterTableCreateToastTable(intoRelationId, toast_options);
+
+ /*
+ * Finally we can open the target table
+ */
+ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+
+ /*
+ * Check INSERT permission on the constructed table.
+ *
+ * XXX: It would arguably make sense to skip this check if into->skipData
+ * is true.
+ */
+ rte = makeNode(RangeTblEntry);
+ rte->rtekind = RTE_RELATION;
+ rte->relid = intoRelationId;
+ rte->relkind = RELKIND_RELATION;
+ rte->requiredPerms = ACL_INSERT;
+
+ for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
+ rte->modifiedCols = bms_add_member(rte->modifiedCols,
+ attnum - FirstLowInvalidHeapAttributeNumber);
+
+ ExecCheckRTPerms(list_make1(rte), true);
+
+ /*
+ * Fill private fields of myState for use by later routines
+ */
+ myState->rel = intoRelationDesc;
+ myState->output_cid = GetCurrentCommandId(true);
+
+ /*
+ * We can skip WAL-logging the insertions, unless PITR or streaming
+ * replication is in use. We can skip the FSM in any case.
+ */
+ myState->hi_options = HEAP_INSERT_SKIP_FSM |
+ (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
+ myState->bistate = GetBulkInsertState();
+
+ /* Not using WAL requires smgr_targblock be initially invalid */
+ Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
+}
+
+/*
+ * intorel_receive --- receive one tuple
+ */
+static void
+intorel_receive(TupleTableSlot *slot, DestReceiver *self)
+{
+ DR_intorel *myState = (DR_intorel *) self;
+ HeapTuple tuple;
+
+ /*
+ * get the heap tuple out of the tuple table slot, making sure we have a
+ * writable copy
+ */
+ tuple = ExecMaterializeSlot(slot);
+
+ /*
+ * force assignment of new OID (see comments in ExecInsert)
+ */
+ if (myState->rel->rd_rel->relhasoids)
+ HeapTupleSetOid(tuple, InvalidOid);
+
+ heap_insert(myState->rel,
+ tuple,
+ myState->output_cid,
+ myState->hi_options,
+ myState->bistate);
+
+ /* We know this is a newly created relation, so there are no indexes */
+}
+
+/*
+ * intorel_shutdown --- executor end
+ */
+static void
+intorel_shutdown(DestReceiver *self)
+{
+ DR_intorel *myState = (DR_intorel *) self;
+
+ FreeBulkInsertState(myState->bistate);
+
+ /* If we skipped using WAL, must heap_sync before commit */
+ if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
+ heap_sync(myState->rel);
+
+ /* close rel, but keep lock until commit */
+ heap_close(myState->rel, NoLock);
+ myState->rel = NULL;
+}
+
+/*
+ * intorel_destroy --- release DestReceiver object
+ */
+static void
+intorel_destroy(DestReceiver *self)
+{
+ pfree(self);
+}
#include "access/xact.h"
#include "catalog/pg_type.h"
+#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/hashjoin.h"
#define X_CLOSE_IMMEDIATE 2
#define X_NOWHITESPACE 4
-static void ExplainOneQuery(Query *query, ExplainState *es,
+static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
/* Explain every plan */
foreach(l, rewritten)
{
- ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
+ ExplainOneQuery((Query *) lfirst(l), NULL, &es,
+ queryString, params);
/* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
/*
* ExplainOneQuery -
* print out the execution plan for one Query
+ *
+ * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
*/
static void
-ExplainOneQuery(Query *query, ExplainState *es,
+ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
- ExplainOneUtility(query->utilityStmt, es, queryString, params);
+ ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
return;
}
/* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook)
- (*ExplainOneQuery_hook) (query, es, queryString, params);
+ (*ExplainOneQuery_hook) (query, into, es, queryString, params);
else
{
PlannedStmt *plan;
plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */
- ExplainOnePlan(plan, es, queryString, params);
+ ExplainOnePlan(plan, into, es, queryString, params);
}
}
* (In general, utility statements don't have plans, but there are some
* we treat as special cases)
*
+ * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
+ *
* This is exported because it's called back from prepare.c in the
- * EXPLAIN EXECUTE case
+ * EXPLAIN EXECUTE case.
*/
void
-ExplainOneUtility(Node *utilityStmt, ExplainState *es,
+ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
if (utilityStmt == NULL)
return;
- if (IsA(utilityStmt, ExecuteStmt))
- ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
+ if (IsA(utilityStmt, CreateTableAsStmt))
+ {
+ /*
+ * We have to rewrite the contained SELECT and then pass it back
+ * to ExplainOneQuery. It's probably not really necessary to copy
+ * the contained parsetree another time, but let's be safe.
+ */
+ CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
+ List *rewritten;
+
+ Assert(IsA(ctas->query, Query));
+ rewritten = QueryRewrite((Query *) copyObject(ctas->query));
+ Assert(list_length(rewritten) == 1);
+ ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
+ queryString, params);
+ }
+ else if (IsA(utilityStmt, ExecuteStmt))
+ ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
queryString, params);
else if (IsA(utilityStmt, NotifyStmt))
{
* given a planned query, execute it if needed, and then print
* EXPLAIN output
*
+ * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
+ * in which case executing the query should result in creating that table.
+ *
* Since we ignore any DeclareCursorStmt that might be attached to the query,
* if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
* query. This is different from pre-8.3 behavior but seems more useful than
* to call it.
*/
void
-ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
+ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
+ DestReceiver *dest;
QueryDesc *queryDesc;
instr_time starttime;
double totaltime = 0;
PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId();
- /* Create a QueryDesc requesting no output */
+ /*
+ * Normally we discard the query's output, but if explaining CREATE TABLE
+ * AS, we'd better use the appropriate tuple receiver.
+ */
+ if (into)
+ dest = CreateIntoRelDestReceiver(into);
+ else
+ dest = None_Receiver;
+
+ /* Create a QueryDesc for the query */
queryDesc = CreateQueryDesc(plannedstmt, queryString,
GetActiveSnapshot(), InvalidSnapshot,
- None_Receiver, params, instrument_option);
+ dest, params, instrument_option);
/* Select execution options */
if (es->analyze)
eflags = 0; /* default run-to-completion flags */
else
eflags = EXEC_FLAG_EXPLAIN_ONLY;
+ if (into)
+ eflags |= GetIntoRelEFlags(into);
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, eflags);
/* Execute the plan for statistics if asked for */
if (es->analyze)
{
+ ScanDirection dir;
+
+ /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
+ if (into && into->skipData)
+ dir = NoMovementScanDirection;
+ else
+ dir = ForwardScanDirection;
+
/* run the plan */
- ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+ ExecutorRun(queryDesc, dir, 0L);
/* run cleanup too */
ExecutorFinish(queryDesc);
/*
* Start execution, inserting parameters if any.
*/
- PortalStart(portal, params, true);
+ PortalStart(portal, params, 0, true);
Assert(portal->strategy == PORTAL_ONE_SELECT);
#include "access/xact.h"
#include "catalog/pg_type.h"
+#include "commands/createas.h"
#include "commands/prepare.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
}
/*
- * Implements the 'EXECUTE' utility statement.
+ * ExecuteQuery --- implement the 'EXECUTE' utility statement.
+ *
+ * This code also supports CREATE TABLE ... AS EXECUTE. That case is
+ * indicated by passing a non-null intoClause. The DestReceiver is already
+ * set up correctly for CREATE TABLE AS, but we still have to make a few
+ * other adjustments here.
*
* Note: this is one of very few places in the code that needs to deal with
* two query strings at once. The passed-in queryString is that of the
* source is that of the original PREPARE.
*/
void
-ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
- ParamListInfo params,
+ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
+ const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag)
{
PreparedStatement *entry;
EState *estate = NULL;
Portal portal;
char *query_string;
+ int eflags;
+ long count;
/* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true);
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
entry->plansource->query_string);
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = GetCachedPlan(entry->plansource, paramLI, false);
+ plan_list = cplan->stmt_list;
+
/*
- * For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query
- * so that we can modify its destination (yech, but this has always been
- * ugly). For regular EXECUTE we can just use the cached query, since the
- * executor is read-only.
+ * For CREATE TABLE ... AS EXECUTE, we must verify that the prepared
+ * statement is one that produces tuples. Currently we insist that it be
+ * a plain old SELECT. In future we might consider supporting other
+ * things such as INSERT ... RETURNING, but there are a couple of issues
+ * to be settled first, notably how WITH NO DATA should be handled in such
+ * a case (do we really want to suppress execution?) and how to pass down
+ * the OID-determining eflags (PortalStart won't handle them in such a
+ * case, and for that matter it's not clear the executor will either).
+ *
+ * For CREATE TABLE ... AS EXECUTE, we also have to ensure that the
+ * proper eflags and fetch count are passed to PortalStart/PortalRun.
*/
- if (stmt->into)
+ if (intoClause)
{
- MemoryContext oldContext;
PlannedStmt *pstmt;
- /* Replan if needed, and increment plan refcount transiently */
- cplan = GetCachedPlan(entry->plansource, paramLI, true);
-
- /* Copy plan into portal's context, and modify */
- oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-
- plan_list = copyObject(cplan->stmt_list);
-
if (list_length(plan_list) != 1)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
- pstmt->intoClause = copyObject(stmt->into);
- MemoryContextSwitchTo(oldContext);
+ /* Set appropriate eflags */
+ eflags = GetIntoRelEFlags(intoClause);
- /* We no longer need the cached plan refcount ... */
- ReleaseCachedPlan(cplan, true);
- /* ... and we don't want the portal to depend on it, either */
- cplan = NULL;
+ /* And tell PortalRun whether to run to completion or not */
+ if (intoClause->skipData)
+ count = 0;
+ else
+ count = FETCH_ALL;
}
else
{
- /* Replan if needed, and increment plan refcount for portal */
- cplan = GetCachedPlan(entry->plansource, paramLI, false);
- plan_list = cplan->stmt_list;
+ /* Plain old EXECUTE */
+ eflags = 0;
+ count = FETCH_ALL;
}
PortalDefineQuery(portal,
cplan);
/*
- * Run the portal to completion.
+ * Run the portal as appropriate.
*/
- PortalStart(portal, paramLI, true);
+ PortalStart(portal, paramLI, eflags, true);
- (void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag);
+ (void) PortalRun(portal, count, false, dest, dest, completionTag);
PortalDrop(portal, false);
/*
* Implements the 'EXPLAIN EXECUTE' utility statement.
*
+ * "into" is NULL unless we are doing EXPLAIN CREATE TABLE AS EXECUTE,
+ * in which case executing the query should result in creating that table.
+ *
* Note: the passed-in queryString is that of the EXPLAIN EXECUTE,
* not the original PREPARE; we get the latter string from the plancache.
*/
void
-ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
+ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
PreparedStatement *entry;
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt))
- {
- if (execstmt->into)
- {
- if (pstmt->commandType != CMD_SELECT ||
- pstmt->utilityStmt != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("prepared statement is not a SELECT")));
-
- /* Copy the stmt so we can modify it */
- pstmt = copyObject(pstmt);
-
- pstmt->intoClause = execstmt->into;
- }
-
- ExplainOnePlan(pstmt, es, query_string, paramLI);
- }
+ ExplainOnePlan(pstmt, into, es, query_string, paramLI);
else
- {
- ExplainOneUtility((Node *) pstmt, es, query_string, paramLI);
- }
+ ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
/*
* The grammar should ensure that the result is a single SELECT Query.
+ * However, it doesn't forbid SELECT INTO, so we have to check for that.
*/
- if (!IsA(viewParse, Query) ||
- viewParse->commandType != CMD_SELECT)
+ if (!IsA(viewParse, Query))
+ elog(ERROR, "unexpected parse analysis result");
+ if (viewParse->utilityStmt != NULL &&
+ IsA(viewParse->utilityStmt, CreateTableAsStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("views must not contain SELECT INTO")));
+ if (viewParse->commandType != CMD_SELECT ||
+ viewParse->utilityStmt != NULL)
elog(ERROR, "unexpected parse analysis result");
/*
* DefineQueryRewrite(), but that function will complain about a bogus ON
* SELECT rule, and we'd rather the message complain about a view.
*/
- if (viewParse->intoClause != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("views must not contain SELECT INTO")));
if (viewParse->hasModifyingCTE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
*/
#include "postgres.h"
-#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/xact.h"
-#include "catalog/heap.h"
#include "catalog/namespace.h"
-#include "catalog/toasting.h"
-#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
-#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
-#include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
-#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
-static void OpenIntoRel(QueryDesc *queryDesc);
-static void CloseIntoRel(QueryDesc *queryDesc);
-static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
-static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
-static void intorel_shutdown(DestReceiver *self);
-static void intorel_destroy(DestReceiver *self);
/* end of local decls */
case CMD_SELECT:
/*
- * SELECT INTO, SELECT FOR UPDATE/SHARE and modifying CTEs need to
- * mark tuples
+ * SELECT FOR UPDATE/SHARE and modifying CTEs need to mark tuples
*/
- if (queryDesc->plannedstmt->intoClause != NULL ||
- queryDesc->plannedstmt->rowMarks != NIL ||
+ if (queryDesc->plannedstmt->rowMarks != NIL ||
queryDesc->plannedstmt->hasModifyingCTE)
estate->es_output_cid = GetCurrentCommandId(true);
if (sendTuples)
(*dest->rStartup) (dest, operation, queryDesc->tupDesc);
- /*
- * if it's CREATE TABLE AS ... WITH NO DATA, skip plan execution
- */
- if (estate->es_select_into &&
- queryDesc->plannedstmt->intoClause->skipData)
- direction = NoMovementScanDirection;
-
/*
* run plan
*/
ExecEndPlan(queryDesc->planstate, estate);
- /*
- * Close the SELECT INTO relation if any
- */
- if (estate->es_select_into)
- CloseIntoRel(queryDesc);
-
/* do away with our snapshots */
UnregisterSnapshot(estate->es_snapshot);
UnregisterSnapshot(estate->es_crosscheck_snapshot);
{
ListCell *l;
- /*
- * CREATE TABLE AS or SELECT INTO?
- *
- * XXX should we allow this if the destination is temp? Considering that
- * it would still require catalog changes, probably not.
- */
- if (plannedstmt->intoClause != NULL)
- PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
-
/* Fail if write permissions are requested on any non-temp table */
foreach(l, plannedstmt->rtable)
{
estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
}
- /*
- * Detect whether we're doing SELECT INTO. If so, set the es_into_oids
- * flag appropriately so that the plan tree will be initialized with the
- * correct tuple descriptors. (Other SELECT INTO stuff comes later.)
- */
- estate->es_select_into = false;
- if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
- {
- estate->es_select_into = true;
- estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
- }
-
/*
* Initialize the executor's tuple table to empty.
*/
planstate = ExecInitNode(plan, estate, eflags);
/*
- * Get the tuple descriptor describing the type of tuples to return. (this
- * is especially important if we are creating a relation with "SELECT
- * INTO")
+ * Get the tuple descriptor describing the type of tuples to return.
*/
tupType = ExecGetResultType(planstate);
queryDesc->tupDesc = tupType;
queryDesc->planstate = planstate;
-
- /*
- * If doing SELECT INTO, initialize the "into" relation. We must wait
- * till now so we have the "clean" result tuple type to create the new
- * table from.
- *
- * If EXPLAIN, skip creating the "into" relation.
- */
- if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY))
- OpenIntoRel(queryDesc);
}
/*
/*
* ExecContextForcesOids
*
- * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO,
+ * This is pretty grotty: when doing INSERT, UPDATE, or CREATE TABLE AS,
* we need to ensure that result tuples have space for an OID iff they are
* going to be stored into a relation that has OIDs. In other contexts
* we are free to choose whether to leave space for OIDs in result tuples
* the ModifyTable node, so ModifyTable has to set es_result_relation_info
* while initializing each subplan.
*
- * SELECT INTO is even uglier, because we don't have the INTO relation's
- * descriptor available when this code runs; we have to look aside at a
- * flag set by InitPlan().
+ * CREATE TABLE AS is even uglier, because we don't have the target relation's
+ * descriptor available when this code runs; we have to look aside at the
+ * flags passed to ExecutorStart().
*/
bool
ExecContextForcesOids(PlanState *planstate, bool *hasoids)
}
}
- if (planstate->state->es_select_into)
+ if (planstate->state->es_top_eflags & EXEC_FLAG_WITH_OIDS)
{
- *hasoids = planstate->state->es_into_oids;
+ *hasoids = true;
+ return true;
+ }
+ if (planstate->state->es_top_eflags & EXEC_FLAG_WITHOUT_OIDS)
+ {
+ *hasoids = false;
return true;
}
estate->es_rowMarks = parentestate->es_rowMarks;
estate->es_top_eflags = parentestate->es_top_eflags;
estate->es_instrument = parentestate->es_instrument;
- estate->es_select_into = parentestate->es_select_into;
- estate->es_into_oids = parentestate->es_into_oids;
/* es_auxmodifytables must NOT be copied */
/*
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
-
-
-/*
- * Support for SELECT INTO (a/k/a CREATE TABLE AS)
- *
- * We implement SELECT INTO by diverting SELECT's normal output with
- * a specialized DestReceiver type.
- */
-
-typedef struct
-{
- DestReceiver pub; /* publicly-known function pointers */
- EState *estate; /* EState we are working with */
- DestReceiver *origdest; /* QueryDesc's original receiver */
- Relation rel; /* Relation to write to */
- int hi_options; /* heap_insert performance options */
- BulkInsertState bistate; /* bulk insert state */
-} DR_intorel;
-
-/*
- * OpenIntoRel --- actually create the SELECT INTO target relation
- *
- * This also replaces QueryDesc->dest with the special DestReceiver for
- * SELECT INTO. We assume that the correct result tuple type has already
- * been placed in queryDesc->tupDesc.
- */
-static void
-OpenIntoRel(QueryDesc *queryDesc)
-{
- IntoClause *into = queryDesc->plannedstmt->intoClause;
- EState *estate = queryDesc->estate;
- TupleDesc intoTupDesc = queryDesc->tupDesc;
- Relation intoRelationDesc;
- char *intoName;
- Oid namespaceId;
- Oid tablespaceId;
- Datum reloptions;
- Oid intoRelationId;
- DR_intorel *myState;
- RangeTblEntry *rte;
- AttrNumber attnum;
- static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
-
- Assert(into);
-
- /*
- * XXX This code needs to be kept in sync with DefineRelation(). Maybe we
- * should try to use that function instead.
- */
-
- /*
- * Check consistency of arguments
- */
- if (into->onCommit != ONCOMMIT_NOOP
- && into->rel->relpersistence != RELPERSISTENCE_TEMP)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("ON COMMIT can only be used on temporary tables")));
-
- {
- AclResult aclresult;
- int i;
-
- for (i = 0; i < intoTupDesc->natts; i++)
- {
- Oid atttypid = intoTupDesc->attrs[i]->atttypid;
-
- aclresult = pg_type_aclcheck(atttypid, GetUserId(), ACL_USAGE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_TYPE,
- format_type_be(atttypid));
- }
- }
-
- /*
- * If a column name list was specified in CREATE TABLE AS, override the
- * column names derived from the query. (Too few column names are OK, too
- * many are not.) It would probably be all right to scribble directly on
- * the query's result tupdesc, but let's be safe and make a copy.
- */
- if (into->colNames)
- {
- ListCell *lc;
-
- intoTupDesc = CreateTupleDescCopy(intoTupDesc);
- attnum = 1;
- foreach(lc, into->colNames)
- {
- char *colname = strVal(lfirst(lc));
-
- if (attnum > intoTupDesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("CREATE TABLE AS specifies too many column names")));
- namestrcpy(&(intoTupDesc->attrs[attnum - 1]->attname), colname);
- attnum++;
- }
- }
-
- /*
- * Find namespace to create in, check its permissions, lock it against
- * concurrent drop, and mark into->rel as RELPERSISTENCE_TEMP if the
- * selected namespace is temporary.
- */
- intoName = into->rel->relname;
- namespaceId = RangeVarGetAndCheckCreationNamespace(into->rel, NoLock,
- NULL);
-
- /*
- * Security check: disallow creating temp tables from security-restricted
- * code. This is needed because calling code might not expect untrusted
- * tables to appear in pg_temp at the front of its search path.
- */
- if (into->rel->relpersistence == RELPERSISTENCE_TEMP
- && InSecurityRestrictedOperation())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot create temporary table within security-restricted operation")));
-
- /*
- * Select tablespace to use. If not specified, use default tablespace
- * (which may in turn default to database's default).
- */
- if (into->tableSpaceName)
- {
- tablespaceId = get_tablespace_oid(into->tableSpaceName, false);
- }
- else
- {
- tablespaceId = GetDefaultTablespace(into->rel->relpersistence);
- /* note InvalidOid is OK in this case */
- }
-
- /* Check permissions except when using the database's default space */
- if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
- {
- AclResult aclresult;
-
- aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
- ACL_CREATE);
-
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
- get_tablespace_name(tablespaceId));
- }
-
- /* Parse and validate any reloptions */
- reloptions = transformRelOptions((Datum) 0,
- into->options,
- NULL,
- validnsps,
- true,
- false);
- (void) heap_reloptions(RELKIND_RELATION, reloptions, true);
-
- /* Now we can actually create the new relation */
- intoRelationId = heap_create_with_catalog(intoName,
- namespaceId,
- tablespaceId,
- InvalidOid,
- InvalidOid,
- InvalidOid,
- GetUserId(),
- intoTupDesc,
- NIL,
- RELKIND_RELATION,
- into->rel->relpersistence,
- false,
- false,
- true,
- 0,
- into->onCommit,
- reloptions,
- true,
- allowSystemTableMods);
- Assert(intoRelationId != InvalidOid);
-
- /*
- * Advance command counter so that the newly-created relation's catalog
- * tuples will be visible to heap_open.
- */
- CommandCounterIncrement();
-
- /*
- * If necessary, create a TOAST table for the INTO relation. Note that
- * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
- * the TOAST table will be visible for insertion.
- */
- reloptions = transformRelOptions((Datum) 0,
- into->options,
- "toast",
- validnsps,
- true,
- false);
-
- (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true);
-
- AlterTableCreateToastTable(intoRelationId, reloptions);
-
- /*
- * And open the constructed table for writing.
- */
- intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
-
- /*
- * Check INSERT permission on the constructed table.
- */
- rte = makeNode(RangeTblEntry);
- rte->rtekind = RTE_RELATION;
- rte->relid = intoRelationId;
- rte->relkind = RELKIND_RELATION;
- rte->requiredPerms = ACL_INSERT;
-
- for (attnum = 1; attnum <= intoTupDesc->natts; attnum++)
- rte->modifiedCols = bms_add_member(rte->modifiedCols,
- attnum - FirstLowInvalidHeapAttributeNumber);
-
- ExecCheckRTPerms(list_make1(rte), true);
-
- /*
- * Now replace the query's DestReceiver with one for SELECT INTO
- */
- myState = (DR_intorel *) CreateDestReceiver(DestIntoRel);
- Assert(myState->pub.mydest == DestIntoRel);
- myState->estate = estate;
- myState->origdest = queryDesc->dest;
- myState->rel = intoRelationDesc;
-
- queryDesc->dest = (DestReceiver *) myState;
-
- /*
- * We can skip WAL-logging the insertions, unless PITR or streaming
- * replication is in use. We can skip the FSM in any case.
- */
- myState->hi_options = HEAP_INSERT_SKIP_FSM |
- (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
- myState->bistate = GetBulkInsertState();
-
- /* Not using WAL requires smgr_targblock be initially invalid */
- Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
-}
-
-/*
- * CloseIntoRel --- clean up SELECT INTO at ExecutorEnd time
- */
-static void
-CloseIntoRel(QueryDesc *queryDesc)
-{
- DR_intorel *myState = (DR_intorel *) queryDesc->dest;
-
- /*
- * OpenIntoRel might never have gotten called, and we also want to guard
- * against double destruction.
- */
- if (myState && myState->pub.mydest == DestIntoRel)
- {
- FreeBulkInsertState(myState->bistate);
-
- /* If we skipped using WAL, must heap_sync before commit */
- if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
- heap_sync(myState->rel);
-
- /* close rel, but keep lock until commit */
- heap_close(myState->rel, NoLock);
-
- /* restore the receiver belonging to executor's caller */
- queryDesc->dest = myState->origdest;
-
- /* might as well invoke my destructor */
- intorel_destroy((DestReceiver *) myState);
- }
-}
-
-/*
- * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
- */
-DestReceiver *
-CreateIntoRelDestReceiver(void)
-{
- DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
-
- self->pub.receiveSlot = intorel_receive;
- self->pub.rStartup = intorel_startup;
- self->pub.rShutdown = intorel_shutdown;
- self->pub.rDestroy = intorel_destroy;
- self->pub.mydest = DestIntoRel;
-
- /* private fields will be set by OpenIntoRel */
-
- return (DestReceiver *) self;
-}
-
-/*
- * intorel_startup --- executor startup
- */
-static void
-intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
-{
- /* no-op */
-}
-
-/*
- * intorel_receive --- receive one tuple
- */
-static void
-intorel_receive(TupleTableSlot *slot, DestReceiver *self)
-{
- DR_intorel *myState = (DR_intorel *) self;
- HeapTuple tuple;
-
- /*
- * get the heap tuple out of the tuple table slot, making sure we have a
- * writable copy
- */
- tuple = ExecMaterializeSlot(slot);
-
- /*
- * force assignment of new OID (see comments in ExecInsert)
- */
- if (myState->rel->rd_rel->relhasoids)
- HeapTupleSetOid(tuple, InvalidOid);
-
- heap_insert(myState->rel,
- tuple,
- myState->estate->es_output_cid,
- myState->hi_options,
- myState->bistate);
-
- /* We know this is a newly created relation, so there are no indexes */
-}
-
-/*
- * intorel_shutdown --- executor end
- */
-static void
-intorel_shutdown(DestReceiver *self)
-{
- /* no-op */
-}
-
-/*
- * intorel_destroy --- release DestReceiver object
- */
-static void
-intorel_destroy(DestReceiver *self)
-{
- pfree(self);
-}
estate->es_top_eflags = 0;
estate->es_instrument = 0;
- estate->es_select_into = false;
- estate->es_into_oids = false;
estate->es_finished = false;
estate->es_exprcontexts = NIL;
if (ps->commandType == CMD_SELECT &&
ps->utilityStmt == NULL &&
- ps->intoClause == NULL &&
!ps->hasModifyingCTE)
fcache->lazyEval = lasttages->lazyEval = true;
}
*/
if (parse &&
parse->commandType == CMD_SELECT &&
- parse->utilityStmt == NULL &&
- parse->intoClause == NULL)
+ parse->utilityStmt == NULL)
{
tlist_ptr = &parse->targetList;
tlist = parse->targetList;
* Start portal execution.
*/
if (read_only)
- PortalStart(portal, paramLI, true);
+ PortalStart(portal, paramLI, 0, true);
else
{
CommandCounterIncrement();
- PortalStart(portal, paramLI, false);
+ PortalStart(portal, paramLI, 0, false);
}
Assert(portal->strategy != PORTAL_MULTI_QUERY);
}
else
{
+ char completionTag[COMPLETION_TAG_BUFSIZE];
+
ProcessUtility(stmt,
plansource->query_string,
paramLI,
false, /* not top level */
dest,
- NULL);
+ completionTag);
+
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced -
_SPI_current->tuptable->free;
- res = SPI_OK_UTILITY;
+
+ /*
+ * CREATE TABLE AS is a messy special case for historical
+ * reasons. We must set _SPI_current->processed even though
+ * the tuples weren't returned to the caller, and we must
+ * return a special result code if the statement was spelled
+ * SELECT INTO.
+ */
+ if (IsA(stmt, CreateTableAsStmt))
+ {
+ Assert(strncmp(completionTag, "SELECT ", 7) == 0);
+ _SPI_current->processed = strtoul(completionTag + 7,
+ NULL, 10);
+ if (((CreateTableAsStmt *) stmt)->is_select_into)
+ res = SPI_OK_SELINTO;
+ else
+ res = SPI_OK_UTILITY;
+ }
+ else
+ res = SPI_OK_UTILITY;
}
/*
{
case CMD_SELECT:
Assert(queryDesc->plannedstmt->utilityStmt == NULL);
- if (queryDesc->plannedstmt->intoClause) /* select into table? */
- res = SPI_OK_SELINTO;
- else if (queryDesc->dest->mydest != DestSPI)
+ if (queryDesc->dest->mydest != DestSPI)
{
/* Don't return SPI_OK_SELECT if we're discarding result */
res = SPI_OK_UTILITY;
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(utilityStmt);
- COPY_NODE_FIELD(intoClause);
COPY_NODE_FIELD(subplans);
COPY_BITMAPSET_FIELD(rewindPlanIDs);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(utilityStmt);
COPY_SCALAR_FIELD(resultRelation);
- COPY_NODE_FIELD(intoClause);
COPY_SCALAR_FIELD(hasAggs);
COPY_SCALAR_FIELD(hasWindowFuncs);
COPY_SCALAR_FIELD(hasSubLinks);
return newnode;
}
+static CreateTableAsStmt *
+_copyCreateTableAsStmt(const CreateTableAsStmt *from)
+{
+ CreateTableAsStmt *newnode = makeNode(CreateTableAsStmt);
+
+ COPY_NODE_FIELD(query);
+ COPY_NODE_FIELD(into);
+ COPY_SCALAR_FIELD(is_select_into);
+
+ return newnode;
+}
+
static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from)
{
ExecuteStmt *newnode = makeNode(ExecuteStmt);
COPY_STRING_FIELD(name);
- COPY_NODE_FIELD(into);
COPY_NODE_FIELD(params);
return newnode;
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
+ case T_CreateTableAsStmt:
+ retval = _copyCreateTableAsStmt(from);
+ break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;
COMPARE_SCALAR_FIELD(canSetTag);
COMPARE_NODE_FIELD(utilityStmt);
COMPARE_SCALAR_FIELD(resultRelation);
- COMPARE_NODE_FIELD(intoClause);
COMPARE_SCALAR_FIELD(hasAggs);
COMPARE_SCALAR_FIELD(hasWindowFuncs);
COMPARE_SCALAR_FIELD(hasSubLinks);
return true;
}
+static bool
+_equalCreateTableAsStmt(const CreateTableAsStmt *a, const CreateTableAsStmt *b)
+{
+ COMPARE_NODE_FIELD(query);
+ COMPARE_NODE_FIELD(into);
+ COMPARE_SCALAR_FIELD(is_select_into);
+
+ return true;
+}
+
static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
_equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b)
{
COMPARE_STRING_FIELD(name);
- COMPARE_NODE_FIELD(into);
COMPARE_NODE_FIELD(params);
return true;
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
+ case T_CreateTableAsStmt:
+ retval = _equalCreateTableAsStmt(a, b);
+ break;
case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b);
break;
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(utilityStmt);
- WRITE_NODE_FIELD(intoClause);
WRITE_NODE_FIELD(subplans);
WRITE_BITMAPSET_FIELD(rewindPlanIDs);
WRITE_NODE_FIELD(rowMarks);
appendStringInfo(str, " :utilityStmt <>");
WRITE_INT_FIELD(resultRelation);
- WRITE_NODE_FIELD(intoClause);
WRITE_BOOL_FIELD(hasAggs);
WRITE_BOOL_FIELD(hasWindowFuncs);
WRITE_BOOL_FIELD(hasSubLinks);
READ_BOOL_FIELD(canSetTag);
READ_NODE_FIELD(utilityStmt);
READ_INT_FIELD(resultRelation);
- READ_NODE_FIELD(intoClause);
READ_BOOL_FIELD(hasAggs);
READ_BOOL_FIELD(hasWindowFuncs);
READ_BOOL_FIELD(hasSubLinks);
result->rtable = glob->finalrtable;
result->resultRelations = glob->resultRelations;
result->utilityStmt = parse->utilityStmt;
- result->intoClause = parse->intoClause;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks;
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
+#include "tcop/utility.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
Query *query = (Query *) node;
ListCell *lc;
- if (query->commandType == CMD_UTILITY)
+ while (query->commandType == CMD_UTILITY)
{
- /* Ignore utility statements, except EXPLAIN */
- if (IsA(query->utilityStmt, ExplainStmt))
- {
- query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
- Assert(IsA(query, Query));
- Assert(query->commandType != CMD_UTILITY);
- }
- else
+ /*
+ * Ignore utility statements, except those (such as EXPLAIN) that
+ * contain a parsed-but-not-planned query.
+ */
+ query = UtilityContainsQuery(query->utilityStmt);
+ if (query == NULL)
return false;
}
* are complex.
*/
if (query->commandType != CMD_SELECT ||
- query->intoClause ||
query->setOperations ||
query->hasAggs ||
query->hasWindowFuncs ||
*/
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
- subquery->utilityStmt != NULL ||
- subquery->intoClause != NULL)
+ subquery->utilityStmt != NULL)
elog(ERROR, "subquery is bogus");
/*
/* Let's just make sure it's a valid subselect ... */
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
- subquery->utilityStmt != NULL ||
- subquery->intoClause != NULL)
+ subquery->utilityStmt != NULL)
elog(ERROR, "subquery is bogus");
/* Is it a set-operation query at all? */
pstate->p_sourcetext = src;
sql_fn_parser_setup(pstate, pinfo);
- querytree = transformStmt(pstate, linitial(raw_parsetree_list));
+ querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
querytree->utilityStmt ||
- querytree->intoClause ||
querytree->hasAggs ||
querytree->hasWindowFuncs ||
querytree->hasSubLinks ||
querytree = linitial(querytree_list);
/*
- * The single command must be a regular results-returning SELECT.
+ * The single command must be a plain SELECT.
*/
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
- querytree->utilityStmt ||
- querytree->intoClause)
+ querytree->utilityStmt)
goto fail;
/*
* utility commands, no locks are obtained here (and if they were, we could
* not be sure we'd still have them at execution). Hence the general rule
* for utility commands is to just dump them into a Query node untransformed.
- * DECLARE CURSOR and EXPLAIN are exceptions because they contain
- * optimizable statements.
+ * DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they
+ * contain optimizable statements, which we should transform.
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
+static Query *transformCreateTableAsStmt(ParseState *pstate,
+ CreateTableAsStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
if (numParams > 0)
parse_fixed_parameters(pstate, paramTypes, numParams);
- query = transformStmt(pstate, parseTree);
+ query = transformTopLevelStmt(pstate, parseTree);
free_parsestate(pstate);
parse_variable_parameters(pstate, paramTypes, numParams);
- query = transformStmt(pstate, parseTree);
+ query = transformTopLevelStmt(pstate, parseTree);
/* make sure all is well with parameter types */
check_variable_parameters(pstate, query);
}
/*
- * transformStmt -
+ * transformTopLevelStmt -
* transform a Parse tree into a Query tree.
+ *
+ * The only thing we do here that we don't do in transformStmt() is to
+ * convert SELECT ... INTO into CREATE TABLE AS. Since utility statements
+ * aren't allowed within larger statements, this is only allowed at the top
+ * of the parse tree, and so we only try it before entering the recursive
+ * transformStmt() processing.
+ */
+Query *
+transformTopLevelStmt(ParseState *pstate, Node *parseTree)
+{
+ if (IsA(parseTree, SelectStmt))
+ {
+ SelectStmt *stmt = (SelectStmt *) parseTree;
+
+ /* If it's a set-operation tree, drill down to leftmost SelectStmt */
+ while (stmt && stmt->op != SETOP_NONE)
+ stmt = stmt->larg;
+ Assert(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL);
+
+ if (stmt->intoClause)
+ {
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+
+ ctas->query = parseTree;
+ ctas->into = stmt->intoClause;
+ ctas->is_select_into = true;
+
+ /*
+ * Remove the intoClause from the SelectStmt. This makes it safe
+ * for transformSelectStmt to complain if it finds intoClause set
+ * (implying that the INTO appeared in a disallowed place).
+ */
+ stmt->intoClause = NULL;
+
+ parseTree = (Node *) ctas;
+ }
+ }
+
+ return transformStmt(pstate, parseTree);
+}
+
+/*
+ * transformStmt -
+ * recursively transform a Parse tree into a Query tree.
*/
Query *
transformStmt(ParseState *pstate, Node *parseTree)
(ExplainStmt *) parseTree);
break;
+ case T_CreateTableAsStmt:
+ result = transformCreateTableAsStmt(pstate,
+ (CreateTableAsStmt *) parseTree);
+ break;
+
default:
/*
break;
case T_ExplainStmt:
+ case T_CreateTableAsStmt:
/* yes, because we must analyze the contained statement */
result = true;
break;
free_parsestate(sub_pstate);
- /* The grammar should have produced a SELECT, but it might have INTO */
+ /* The grammar should have produced a SELECT */
if (!IsA(selectQuery, Query) ||
selectQuery->commandType != CMD_SELECT ||
selectQuery->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");
- if (selectQuery->intoClause)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("INSERT ... SELECT cannot specify INTO"),
- parser_errposition(pstate,
- exprLocation((Node *) selectQuery->intoClause))));
/*
* Make the source be a subquery in the INSERT's rangetable, and add
int sublist_length = -1;
int i;
+ Assert(selectStmt->intoClause == NULL);
+
foreach(lc, selectStmt->valuesLists)
{
List *sublist = (List *) lfirst(lc);
List *valuesLists = selectStmt->valuesLists;
Assert(list_length(valuesLists) == 1);
+ Assert(selectStmt->intoClause == NULL);
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate,
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
+ /* Complain if we get called from someplace where INTO is not allowed */
+ if (stmt->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT ... INTO is not allowed here"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
pstate->p_windowdefs,
&qry->targetList);
- /* SELECT INTO/CREATE TABLE AS spec is just passed through */
- qry->intoClause = stmt->intoClause;
-
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
/* Most SELECT stuff doesn't apply in a VALUES clause */
Assert(stmt->distinctClause == NIL);
+ Assert(stmt->intoClause == NULL);
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
- /* CREATE TABLE AS spec is just passed through */
- qry->intoClause = stmt->intoClause;
-
/*
* There mustn't have been any table references in the expressions, else
* strange things would happen, like Cartesian products of those tables
}
/*
- * Find leftmost leaf SelectStmt; extract the one-time-only items from it
- * and from the top-level node.
+ * Find leftmost leaf SelectStmt. We currently only need to do this in
+ * order to deliver a suitable error message if there's an INTO clause
+ * there, implying the set-op tree is in a context that doesn't allow
+ * INTO. (transformSetOperationTree would throw error anyway, but it
+ * seems worth the trouble to throw a different error for non-leftmost
+ * INTO, so we produce that error in transformSetOperationTree.)
*/
leftmostSelect = stmt->larg;
while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
leftmostSelect = leftmostSelect->larg;
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
leftmostSelect->larg == NULL);
- qry->intoClause = leftmostSelect->intoClause;
-
- /* clear this to prevent complaints in transformSetOperationTree() */
- leftmostSelect->intoClause = NULL;
+ if (leftmostSelect->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT ... INTO is not allowed here"),
+ parser_errposition(pstate,
+ exprLocation((Node *) leftmostSelect->intoClause))));
/*
- * These are not one-time, exactly, but we want to process them here and
+ * We need to extract ORDER BY and other top-level clauses here and
* not let transformSetOperationTree() see them --- else it'll just
* recurse right back here!
*/
result->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
- /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
- if (result->intoClause)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("DECLARE CURSOR cannot specify INTO"),
- parser_errposition(pstate,
- exprLocation((Node *) result->intoClause))));
-
/*
* We also disallow data-modifying WITH in a cursor. (This could be
* allowed, but the semantics of when the updates occur might be
{
Query *result;
+ /* transform contained query, allowing SELECT INTO */
+ stmt->query = (Node *) transformTopLevelStmt(pstate, stmt->query);
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+
+/*
+ * transformCreateTableAsStmt -
+ * transform a CREATE TABLE AS (or SELECT ... INTO) Statement
+ *
+ * As with EXPLAIN, transform the contained statement now.
+ */
+static Query *
+transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
+{
+ Query *result;
+
/* transform contained query */
stmt->query = (Node *) transformStmt(pstate, stmt->query);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
static List *extractArgTypes(List *parameters);
-static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount,
;
-/*
- * Note: CREATE TABLE ... AS SELECT ... is just another spelling for
- * SELECT ... INTO.
- */
+/*****************************************************************************
+ *
+ * QUERY :
+ * CREATE TABLE relname AS SelectStmt [ WITH [NO] DATA ]
+ *
+ *
+ * Note: SELECT ... INTO is a now-deprecated alternative for this.
+ *
+ *****************************************************************************/
CreateAsStmt:
CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data
{
- /*
- * When the SelectStmt is a set-operation tree, we must
- * stuff the INTO information into the leftmost component
- * Select, because that's where analyze.c will expect
- * to find it.
- */
- SelectStmt *n = findLeftmostSelect((SelectStmt *) $6);
- if (n->intoClause != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("CREATE TABLE AS cannot specify INTO"),
- parser_errposition(exprLocation((Node *) n->intoClause))));
- n->intoClause = $4;
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+ ctas->query = $6;
+ ctas->into = $4;
+ ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($7);
- $$ = $6;
+ $$ = (Node *) ctas;
}
;
ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $2;
n->params = $3;
- n->into = NULL;
$$ = (Node *) n;
}
| CREATE OptTemp TABLE create_as_target AS
EXECUTE name execute_param_clause opt_with_data
{
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $7;
n->params = $8;
- n->into = $4;
+ ctas->query = (Node *) n;
+ ctas->into = $4;
+ ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($9);
- $$ = (Node *) n;
+ $$ = (Node *) ctas;
}
;
return result;
}
-/* findLeftmostSelect()
- * Find the leftmost component SelectStmt in a set-operation parsetree.
- */
-static SelectStmt *
-findLeftmostSelect(SelectStmt *node)
-{
- while (node && node->op != SETOP_NONE)
- node = node->larg;
- Assert(node && IsA(node, SelectStmt) && node->larg == NULL);
- return node;
-}
-
/* insertSelectOptions()
* Insert ORDER BY, etc into an already-constructed SelectStmt.
*
query->commandType != CMD_SELECT ||
query->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
- if (query->intoClause)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("subquery in FROM cannot have SELECT INTO"),
- parser_errposition(pstate,
- exprLocation((Node *) query->intoClause))));
/*
* The subquery cannot make use of any variables from FROM items created
if (query->utilityStmt != NULL)
elog(ERROR, "unexpected utility statement in WITH");
- if (query->intoClause)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("subquery in WITH cannot have SELECT INTO"),
- parser_errposition(pstate,
- exprLocation((Node *) query->intoClause))));
-
/*
* We disallow data-modifying WITH except at the top level of a query,
* because it's not clear when such a modification should be executed.
qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in SubLink");
- if (qtree->intoClause)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("subquery cannot have SELECT INTO"),
- parser_errposition(pstate,
- exprLocation((Node *) qtree->intoClause))));
sublink->subselect = (Node *) qtree;
query = (Query *) linitial(action);
if (!is_instead ||
query->commandType != CMD_SELECT ||
- query->utilityStmt != NULL ||
- query->intoClause != NULL)
+ query->utilityStmt != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rules on SELECT must have action INSTEAD SELECT")));
#include "access/printtup.h"
#include "access/xact.h"
#include "commands/copy.h"
-#include "executor/executor.h"
+#include "commands/createas.h"
#include "executor/functions.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
return CreateTuplestoreDestReceiver();
case DestIntoRel:
- return CreateIntoRelDestReceiver();
+ return CreateIntoRelDestReceiver(NULL);
case DestCopyOut:
return CreateCopyDestReceiver();
pstate->p_sourcetext = query_string;
(*parserSetup) (pstate, parserSetupArg);
- query = transformStmt(pstate, parsetree);
+ query = transformTopLevelStmt(pstate, parsetree);
free_parsestate(pstate);
* end up being able to do this, keeping the parse/plan snapshot around
* until after we start the portal doesn't cost much.
*/
- PortalStart(portal, NULL, snapshot_set);
+ PortalStart(portal, NULL, 0, snapshot_set);
/* Done with the snapshot used for parsing/planning */
if (snapshot_set)
* for query execution (currently, reuse will only occur if
* PORTAL_ONE_SELECT mode is chosen).
*/
- PortalStart(portal, params, snapshot_set);
+ PortalStart(portal, params, 0, snapshot_set);
/* Done with the snapshot used for parameter I/O and parsing/planning */
if (snapshot_set)
if (query->canSetTag)
{
if (query->commandType == CMD_SELECT &&
- query->utilityStmt == NULL &&
- query->intoClause == NULL)
+ query->utilityStmt == NULL)
{
if (query->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH;
if (pstmt->canSetTag)
{
if (pstmt->commandType == CMD_SELECT &&
- pstmt->utilityStmt == NULL &&
- pstmt->intoClause == NULL)
+ pstmt->utilityStmt == NULL)
{
if (pstmt->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH;
else
{
if (query->commandType == CMD_SELECT &&
- query->utilityStmt == NULL &&
- query->intoClause == NULL)
+ query->utilityStmt == NULL)
return query->targetList;
if (query->returningList)
return query->returningList;
PlannedStmt *pstmt = (PlannedStmt *) stmt;
if (pstmt->commandType == CMD_SELECT &&
- pstmt->utilityStmt == NULL &&
- pstmt->intoClause == NULL)
+ pstmt->utilityStmt == NULL)
return pstmt->planTree->targetlist;
if (pstmt->hasReturning)
return pstmt->planTree->targetlist;
ExecuteStmt *estmt = (ExecuteStmt *) stmt;
PreparedStatement *entry;
- Assert(!estmt->into);
entry = FetchPreparedStatement(estmt->name, true);
return FetchPreparedStatementTargetList(entry);
}
* Prepare a portal for execution.
*
* Caller must already have created the portal, done PortalDefineQuery(),
- * and adjusted portal options if needed. If parameters are needed by
- * the query, they must be passed in here (caller is responsible for
- * giving them appropriate lifetime).
+ * and adjusted portal options if needed.
+ *
+ * If parameters are needed by the query, they must be passed in "params"
+ * (caller is responsible for giving them appropriate lifetime).
+ *
+ * The caller can also provide an initial set of "eflags" to be passed to
+ * ExecutorStart (but note these can be modified internally, and they are
+ * currently only honored for PORTAL_ONE_SELECT portals). Most callers
+ * should simply pass zero.
*
* The use_active_snapshot parameter is currently used only for
* PORTAL_ONE_SELECT portals. If it is true, the active snapshot will
* tupdesc (if any) is known.
*/
void
-PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot)
+PortalStart(Portal portal, ParamListInfo params,
+ int eflags, bool use_active_snapshot)
{
Portal saveActivePortal;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
QueryDesc *queryDesc;
- int eflags;
+ int myeflags;
AssertArg(PortalIsValid(portal));
AssertState(portal->status == PORTAL_DEFINED);
/*
* If it's a scrollable cursor, executor needs to support
- * REWIND and backwards scan.
+ * REWIND and backwards scan, as well as whatever the caller
+ * might've asked for.
*/
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
- eflags = EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
+ myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
else
- eflags = 0; /* default run-to-completion flags */
+ myeflags = eflags;
/*
* Call ExecutorStart to prepare the plan for execution
*/
- ExecutorStart(queryDesc, eflags);
+ ExecutorStart(queryDesc, myeflags);
/*
* This tells PortalCleanup to shut down the executor
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
#include "commands/copy.h"
+#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
switch (stmt->commandType)
{
case CMD_SELECT:
- if (stmt->intoClause != NULL)
- return false; /* SELECT INTO */
- else if (stmt->rowMarks != NIL)
+ if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */
else if (stmt->hasModifyingCTE)
return false; /* data-modifying CTE */
case T_CreateSchemaStmt:
case T_CreateSeqStmt:
case T_CreateStmt:
+ case T_CreateTableAsStmt:
case T_CreateTableSpaceStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
break;
case T_ExecuteStmt:
- ExecuteQuery((ExecuteStmt *) parsetree, queryString, params,
+ ExecuteQuery((ExecuteStmt *) parsetree, NULL,
+ queryString, params,
dest, completionTag);
break;
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
+ case T_CreateTableAsStmt:
+ ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+ queryString, params, completionTag);
+ break;
+
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree);
break;
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry;
- if (stmt->into)
- return false;
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return false; /* not our business to raise error */
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry;
- if (stmt->into)
- return NULL;
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return NULL; /* not our business to raise error */
switch (parsetree->commandType)
{
case CMD_SELECT:
- /* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */
- if (parsetree->utilityStmt == NULL &&
- parsetree->intoClause == NULL)
+ /* returns tuples ... unless it's DECLARE CURSOR */
+ if (parsetree->utilityStmt == NULL)
return true;
break;
case CMD_INSERT:
#endif
+/*
+ * UtilityContainsQuery
+ * Return the contained Query, or NULL if there is none
+ *
+ * Certain utility statements, such as EXPLAIN, contain a Query.
+ * This function encapsulates knowledge of exactly which ones do.
+ * We assume it is invoked only on already-parse-analyzed statements
+ * (else the contained parsetree isn't a Query yet).
+ */
+Query *
+UtilityContainsQuery(Node *parsetree)
+{
+ switch (nodeTag(parsetree))
+ {
+ case T_ExplainStmt:
+ Assert(IsA(((ExplainStmt *) parsetree)->query, Query));
+ return (Query *) ((ExplainStmt *) parsetree)->query;
+
+ case T_CreateTableAsStmt:
+ /* might or might not contain a Query ... */
+ if (IsA(((CreateTableAsStmt *) parsetree)->query, Query))
+ return (Query *) ((CreateTableAsStmt *) parsetree)->query;
+ Assert(IsA(((CreateTableAsStmt *) parsetree)->query, ExecuteStmt));
+ return NULL;
+
+ default:
+ return NULL;
+ }
+}
+
+
/*
* AlterObjectTypeCommandTag
* helper function for CreateCommandTag
tag = "EXPLAIN";
break;
+ case T_CreateTableAsStmt:
+ if (((CreateTableAsStmt *) parsetree)->is_select_into)
+ tag = "SELECT INTO";
+ else
+ tag = "CREATE TABLE AS";
+ break;
+
case T_VariableSetStmt:
switch (((VariableSetStmt *) parsetree)->kind)
{
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR";
}
- else if (stmt->intoClause != NULL)
- tag = "SELECT INTO";
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR";
}
- else if (stmt->intoClause != NULL)
- tag = "SELECT INTO";
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
case T_SelectStmt:
if (((SelectStmt *) parsetree)->intoClause)
- lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
+ lev = LOGSTMT_DDL; /* SELECT INTO */
else
lev = LOGSTMT_ALL;
break;
}
break;
+ case T_CreateTableAsStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
switch (stmt->commandType)
{
case CMD_SELECT:
- if (stmt->intoClause != NULL)
- lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
- else
- lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
+ lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:
switch (stmt->commandType)
{
case CMD_SELECT:
- if (stmt->intoClause != NULL)
- lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
- else
- lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
+ lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:
if (!IsA(plannedstmt, PlannedStmt))
{
/*
- * Ignore utility statements, except EXPLAIN which contains a
- * parsed-but-not-planned query. Note: it's okay to use
+ * Ignore utility statements, except those (such as EXPLAIN) that
+ * contain a parsed-but-not-planned query. Note: it's okay to use
* ScanQueryForLocks, even though the query hasn't been through
* rule rewriting, because rewriting doesn't change the query
* representation.
*/
- if (IsA(plannedstmt, ExplainStmt))
- {
- Query *query;
+ Query *query = UtilityContainsQuery((Node *) plannedstmt);
- query = (Query *) ((ExplainStmt *) plannedstmt)->query;
- Assert(IsA(query, Query));
+ if (query)
ScanQueryForLocks(query, acquire);
- }
continue;
}
if (query->commandType == CMD_UTILITY)
{
- /* Ignore utility statements, except EXPLAIN */
- if (IsA(query->utilityStmt, ExplainStmt))
- {
- query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
- Assert(IsA(query, Query));
+ /* Ignore utility statements, unless they contain a Query */
+ query = UtilityContainsQuery(query->utilityStmt);
+ if (query)
ScanQueryForLocks(query, acquire);
- }
continue;
}
* aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements
* since they have no plans anyway. So invalidate it only if it
- * contains at least one non-utility statement. (EXPLAIN counts as a
- * non-utility statement, though, since it contains an analyzed query
- * that might have dependencies.)
+ * contains at least one non-utility statement, or contains a utility
+ * statement that contains a pre-analyzed query (which could have
+ * dependencies.)
*/
foreach(lc, plansource->query_list)
{
Assert(IsA(query, Query));
if (query->commandType != CMD_UTILITY ||
- IsA(query->utilityStmt, ExplainStmt))
+ UtilityContainsQuery(query->utilityStmt))
{
/* non-utility statement, so invalidate */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
+ /* no need to look further */
break;
}
}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201203111
+#define CATALOG_VERSION_NO 201203191
#endif
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * createas.h
+ * prototypes for createas.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/createas.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CREATEAS_H
+#define CREATEAS_H
+
+#include "nodes/params.h"
+#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
+
+
+extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+ ParamListInfo params, char *completionTag);
+
+extern int GetIntoRelEFlags(IntoClause *intoClause);
+
+extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
+
+#endif /* CREATEAS_H */
/* Hook for plugins to get control in ExplainOneQuery() */
typedef void (*ExplainOneQuery_hook_type) (Query *query,
+ IntoClause *into,
ExplainState *es,
const char *queryString,
ParamListInfo params);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
-extern void ExplainOneUtility(Node *utilityStmt, ExplainState *es,
+extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
+ ExplainState *es,
const char *queryString, ParamListInfo params);
-extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
+extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
+ ExplainState *es,
const char *queryString, ParamListInfo params);
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
extern void PrepareQuery(PrepareStmt *stmt, const char *queryString);
-extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
- ParamListInfo params,
+extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
+ const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag);
extern void DeallocateQuery(DeallocateStmt *stmt);
-extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
+extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into,
+ ExplainState *es,
const char *queryString, ParamListInfo params);
/* Low-level access to stored prepared statements */
extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
-void DropAllPreparedStatements(void);
+extern void DropAllPreparedStatements(void);
#endif /* PREPARE_H */
*
* EXPLAIN_ONLY indicates that the plan tree is being initialized just so
* EXPLAIN can print it out; it will not be run. Hence, no side-effects
- * of startup should occur (such as creating a SELECT INTO target table).
- * However, error checks (such as permission checks) should be performed.
+ * of startup should occur. However, error checks (such as permission checks)
+ * should be performed.
*
* REWIND indicates that the plan node should try to efficiently support
* rescans without parameter changes. (Nodes must support ExecReScan calls
* AfterTriggerBeginQuery/AfterTriggerEndQuery. This does not necessarily
* mean that the plan can't queue any AFTER triggers; just that the caller
* is responsible for there being a trigger context for them to be queued in.
+ *
+ * WITH/WITHOUT_OIDS tell the executor to emit tuples with or without space
+ * for OIDs, respectively. These are currently used only for CREATE TABLE AS.
+ * If neither is set, the plan may or may not produce tuples including OIDs.
*/
#define EXEC_FLAG_EXPLAIN_ONLY 0x0001 /* EXPLAIN, no ANALYZE */
#define EXEC_FLAG_REWIND 0x0002 /* need efficient rescan */
#define EXEC_FLAG_BACKWARD 0x0004 /* need backward scan */
#define EXEC_FLAG_MARK 0x0008 /* need mark/restore */
#define EXEC_FLAG_SKIP_TRIGGERS 0x0010 /* skip AfterTrigger calls */
+#define EXEC_FLAG_WITH_OIDS 0x0020 /* force OIDs in returned tuples */
+#define EXEC_FLAG_WITHOUT_OIDS 0x0040 /* force no OIDs in returned tuples */
/*
extern TupleTableSlot *EvalPlanQualNext(EPQState *epqstate);
extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate);
extern void EvalPlanQualEnd(EPQState *epqstate);
-extern DestReceiver *CreateIntoRelDestReceiver(void);
/*
* prototypes from functions in execProcnode.c
int es_top_eflags; /* eflags passed to ExecutorStart */
int es_instrument; /* OR of InstrumentOption flags */
- bool es_select_into; /* true if doing SELECT INTO */
- bool es_into_oids; /* true to generate OIDs in SELECT INTO */
bool es_finished; /* true when ExecutorFinish is done */
List *es_exprcontexts; /* List of ExprContexts within EState */
T_DropdbStmt,
T_VacuumStmt,
T_ExplainStmt,
+ T_CreateTableAsStmt,
T_CreateSeqStmt,
T_AlterSeqStmt,
T_VariableSetStmt,
/*
* Query -
- * Parse analysis turns all statements into a Query tree (via transformStmt)
+ * Parse analysis turns all statements into a Query tree
* for further processing by the rewriter and planner.
*
* Utility statements (i.e. non-optimizable statements) have the
int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */
- IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
-
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
bool hasSubLinks; /* has subquery SubLink */
*/
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
- IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
+ IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
List *options; /* list of DefElem nodes */
} ExplainStmt;
+/* ----------------------
+ * CREATE TABLE AS Statement (a/k/a SELECT INTO)
+ *
+ * A query written as CREATE TABLE AS will produce this node type natively.
+ * A query written as SELECT ... INTO will be transformed to this form during
+ * parse analysis.
+ *
+ * The "query" field is handled similarly to EXPLAIN, though note that it
+ * can be a SELECT or an EXECUTE, but not other DML statements.
+ * ----------------------
+ */
+typedef struct CreateTableAsStmt
+{
+ NodeTag type;
+ Node *query; /* the query (see comments above) */
+ IntoClause *into; /* destination table */
+ bool is_select_into; /* it was written as SELECT INTO */
+} CreateTableAsStmt;
+
/* ----------------------
* Checkpoint Statement
* ----------------------
{
NodeTag type;
char *name; /* The name of the plan to execute */
- IntoClause *into; /* Optional table to store results in */
List *params; /* Values to assign to parameters */
} ExecuteStmt;
Node *utilityStmt; /* non-null if this is DECLARE CURSOR */
- IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
-
List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */
extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
CommonTableExpr *parentCTE,
bool locked_from_parent);
+
+extern Query *transformTopLevelStmt(ParseState *pstate, Node *parseTree);
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
extern bool analyze_requires_snapshot(Node *parseTree);
extern List *FetchStatementTargetList(Node *stmt);
extern void PortalStart(Portal portal, ParamListInfo params,
- bool use_active_snapshot);
+ int eflags, bool use_active_snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
+extern Query *UtilityContainsQuery(Node *parsetree);
+
extern const char *CreateCommandTag(Node *parsetree);
extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
* We want to disallow SELECT INTO for now, because its behavior
* is not consistent with SELECT INTO in a normal plpgsql context.
* (We need to reimplement EXECUTE to parse the string as a
- * plpgsql command, not just feed it to SPI_execute.) However,
- * CREATE AS should be allowed ... and since it produces the same
- * parsetree as SELECT INTO, there's no way to tell the difference
- * except to look at the source text. Wotta kluge!
+ * plpgsql command, not just feed it to SPI_execute.) This is not
+ * a functional limitation because CREATE TABLE AS is allowed.
*/
- {
- char *ptr;
-
- for (ptr = querystr; *ptr; ptr++)
- if (!scanner_isspace(*ptr))
- break;
- if (*ptr == 'S' || *ptr == 's')
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("EXECUTE of SELECT ... INTO is not implemented"),
- errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
- break;
- }
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+ errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
+ break;
/* Some SPI errors deserve specific error messages */
case SPI_ERROR_COPY:
*/
if (!IsA(query, Query))
return;
- if (query->commandType != CMD_SELECT || query->intoClause)
+ if (query->commandType != CMD_SELECT)
return;
if (query->rtable != NIL)
return;
*/
if (!IsA(stmt, PlannedStmt))
return;
- if (stmt->commandType != CMD_SELECT || stmt->intoClause)
+ if (stmt->commandType != CMD_SELECT)
return;
plan = stmt->planTree;
if (!IsA(plan, Result))
(5 rows)
DROP TABLE created_table;
+--
+-- Disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;
+ ^
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
+ERROR: COPY (SELECT INTO) is not supported
+SELECT * FROM (SELECT 1 INTO f) bar;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: SELECT * FROM (SELECT 1 INTO f) bar;
+ ^
+CREATE VIEW foo AS SELECT 1 INTO b;
+ERROR: views must not contain SELECT INTO
+INSERT INTO b SELECT 1 INTO f;
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: INSERT INTO b SELECT 1 INTO f;
+ ^
(0 rows)
CREATE TABLE test AS SELECT * FROM writetest; -- fail
-ERROR: cannot execute SELECT INTO in a read-only transaction
+ERROR: cannot execute CREATE TABLE AS in a read-only transaction
START TRANSACTION READ WRITE;
DROP TABLE writetest; -- ok
COMMIT;
SELECT * FROM created_table;
DROP TABLE created_table;
+
+--
+-- Disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
+SELECT * FROM (SELECT 1 INTO f) bar;
+CREATE VIEW foo AS SELECT 1 INTO b;
+INSERT INTO b SELECT 1 INTO f;