]> granicus.if.org Git - postgresql/commitdiff
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 20 Mar 2012 01:37:19 +0000 (21:37 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 20 Mar 2012 01:38:12 +0000 (21:38 -0400)
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements.  The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.

In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs.  Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.

Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.

Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn".  There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.

Andres Freund and Tom Lane

47 files changed:
src/backend/commands/Makefile
src/backend/commands/copy.c
src/backend/commands/createas.c [new file with mode: 0644]
src/backend/commands/explain.c
src/backend/commands/portalcmds.c
src/backend/commands/prepare.c
src/backend/commands/view.c
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_cte.c
src/backend/parser/parse_expr.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/dest.c
src/backend/tcop/postgres.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/backend/utils/cache/plancache.c
src/include/catalog/catversion.h
src/include/commands/createas.h [new file with mode: 0644]
src/include/commands/explain.h
src/include/commands/prepare.h
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/nodes/plannodes.h
src/include/parser/analyze.h
src/include/tcop/pquery.h
src/include/tcop/utility.h
src/pl/plpgsql/src/pl_exec.c
src/test/regress/expected/select_into.out
src/test/regress/expected/transactions.out
src/test/regress/sql/select_into.sql

index 4af7aad00b5b8d5d89e99da625096bc5174a7475..9573a0db45be9c04d8a0e9cc4acada21caf6c18b 100644 (file)
@@ -13,7 +13,7 @@ top_builddir = ../../..
 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 \
index 517660d3735d75724e1038c14823ab13b6f81ce4..6b5bcd83c5bbcac9234d727734499aa63a0a7a49 100644 (file)
@@ -1210,15 +1210,17 @@ BeginCopy(bool is_from,
                        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);
 
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
new file mode 100644 (file)
index 0000000..5173f5a
--- /dev/null
@@ -0,0 +1,423 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+}
index 93b1f34ca0c625ec70a7ca4baeebd41d4f189a1d..a14cae144205a86956289e2f74c78350a8960553 100644 (file)
@@ -15,6 +15,7 @@
 
 #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"
@@ -45,7 +46,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
 #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);
@@ -212,7 +213,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
                /* 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)
@@ -288,21 +290,23 @@ ExplainResultDesc(ExplainStmt *stmt)
 /*
  * 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;
@@ -311,7 +315,7 @@ ExplainOneQuery(Query *query, ExplainState *es,
                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);
        }
 }
 
@@ -321,18 +325,36 @@ ExplainOneQuery(Query *query, ExplainState *es,
  *       (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))
        {
@@ -356,6 +378,9 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
  *             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
@@ -366,9 +391,10 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
  * 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;
@@ -392,16 +418,27 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
        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);
@@ -409,8 +446,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
        /* 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);
index 1c7a1c3a33f44caa0d79fafb908734c4a59bd54d..e402042332569b5599de18fd7171c11e65038298 100644 (file)
@@ -121,7 +121,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
        /*
         * Start execution, inserting parameters if any.
         */
-       PortalStart(portal, params, true);
+       PortalStart(portal, params, 0, true);
 
        Assert(portal->strategy == PORTAL_ONE_SELECT);
 
index 4883abe470ed93922fb10515c416fe1dd9e9ecca..edd646e7c348f01ff4304c57aaa3873af2b65f09 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "commands/createas.h"
 #include "commands/prepare.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -170,7 +171,12 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
 }
 
 /*
- * 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
@@ -179,8 +185,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
  * 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;
@@ -190,6 +196,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
        EState     *estate = NULL;
        Portal          portal;
        char       *query_string;
+       int                     eflags;
+       long            count;
 
        /* Look it up in the hash table */
        entry = FetchPreparedStatement(stmt->name, true);
@@ -222,25 +230,27 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
        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),
@@ -252,20 +262,21 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
                        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,
@@ -276,11 +287,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
                                          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);
 
@@ -615,11 +626,14 @@ DropAllPreparedStatements(void)
 /*
  * 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;
@@ -665,27 +679,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
                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 */
 
index 99fb7dbb8f4d0aa2dd6e10883cbfb1cc9b333c53..c887961bc9751c4ec3296573fbcec8fef37f5c16 100644 (file)
@@ -439,9 +439,17 @@ DefineView(ViewStmt *stmt, const char *queryString)
 
        /*
         * 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");
 
        /*
@@ -449,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString)
         * 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),
index 36dcc8e4b5d2e24abca9f07b8dcd673200ca745d..fbb36fa6dc460654a715213d55dbcbdddefec893 100644 (file)
  */
 #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"
@@ -90,12 +83,6 @@ static char *ExecBuildSlotValueDescription(TupleTableSlot *slot,
                                                                                   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 */
 
@@ -174,11 +161,9 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
                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);
 
@@ -309,13 +294,6 @@ standard_ExecutorRun(QueryDesc *queryDesc,
        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
         */
@@ -451,12 +429,6 @@ standard_ExecutorEnd(QueryDesc *queryDesc)
 
        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);
@@ -706,15 +678,6 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 {
        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)
        {
@@ -863,18 +826,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
                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.
         */
@@ -926,9 +877,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
        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);
 
@@ -968,16 +917,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
        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);
 }
 
 /*
@@ -1230,7 +1169,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 /*
  *             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
@@ -1255,9 +1194,9 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
  * 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)
@@ -1275,9 +1214,14 @@ 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;
        }
 
@@ -2290,8 +2234,6 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
        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 */
 
        /*
@@ -2423,351 +2365,3 @@ EvalPlanQualEnd(EPQState *epqstate)
        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);
-}
index 6db42e7b9709fe84246b26f285d94949dab2301d..40cd5ce5d19d627141549769233b2c6215fda7bb 100644 (file)
@@ -137,8 +137,6 @@ CreateExecutorState(void)
 
        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;
index 61f462254ff55cc0bfed5be2db486e1257baa527..ae8d374db216328f6db9eebe3a0f92ea18f93864 100644 (file)
@@ -535,7 +535,6 @@ init_execution_state(List *queryTree_list,
 
                        if (ps->commandType == CMD_SELECT &&
                                ps->utilityStmt == NULL &&
-                               ps->intoClause == NULL &&
                                !ps->hasModifyingCTE)
                                fcache->lazyEval = lasttages->lazyEval = true;
                }
@@ -1493,8 +1492,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
         */
        if (parse &&
                parse->commandType == CMD_SELECT &&
-               parse->utilityStmt == NULL &&
-               parse->intoClause == NULL)
+               parse->utilityStmt == NULL)
        {
                tlist_ptr = &parse->targetList;
                tlist = parse->targetList;
index 81f284ca0446fc9a099f68d90cac2dff7a1a5cd4..5e4ae426b1ba03ef97317dd83271bc8e757180ff 100644 (file)
@@ -1284,11 +1284,11 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
         * 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);
@@ -1907,17 +1907,39 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                        }
                        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;
                        }
 
                        /*
@@ -2042,9 +2064,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount)
        {
                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;
index 5cde22543f5b7d4d607224acfa22a604a419ed63..cf23b0887242f4d3a008599b5464bade86cc412b 100644 (file)
@@ -86,7 +86,6 @@ _copyPlannedStmt(const PlannedStmt *from)
        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);
@@ -2406,7 +2405,6 @@ _copyQuery(const Query *from)
        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);
@@ -3194,6 +3192,18 @@ _copyExplainStmt(const ExplainStmt *from)
        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)
 {
@@ -3602,7 +3612,6 @@ _copyExecuteStmt(const ExecuteStmt *from)
        ExecuteStmt *newnode = makeNode(ExecuteStmt);
 
        COPY_STRING_FIELD(name);
-       COPY_NODE_FIELD(into);
        COPY_NODE_FIELD(params);
 
        return newnode;
@@ -4234,6 +4243,9 @@ copyObject(const void *from)
                case T_ExplainStmt:
                        retval = _copyExplainStmt(from);
                        break;
+               case T_CreateTableAsStmt:
+                       retval = _copyCreateTableAsStmt(from);
+                       break;
                case T_CreateSeqStmt:
                        retval = _copyCreateSeqStmt(from);
                        break;
index d2a79eb851c8cf7134a6f0decc7045de4f667c93..5e691f96f79ac1e0a4177ab3edd42b3ab265d895 100644 (file)
@@ -900,7 +900,6 @@ _equalQuery(const Query *a, const Query *b)
        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);
@@ -1564,6 +1563,16 @@ _equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b)
        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)
 {
@@ -1908,7 +1917,6 @@ static bool
 _equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b)
 {
        COMPARE_STRING_FIELD(name);
-       COMPARE_NODE_FIELD(into);
        COMPARE_NODE_FIELD(params);
 
        return true;
@@ -2793,6 +2801,9 @@ equal(const void *a, const void *b)
                case T_ExplainStmt:
                        retval = _equalExplainStmt(a, b);
                        break;
+               case T_CreateTableAsStmt:
+                       retval = _equalCreateTableAsStmt(a, b);
+                       break;
                case T_CreateSeqStmt:
                        retval = _equalCreateSeqStmt(a, b);
                        break;
index 51181a9a7438e8609ab922340d1c2d20ba73726d..e925434eb3943fa5dda043d45141e8fd21b9a658 100644 (file)
@@ -250,7 +250,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
        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);
@@ -2181,7 +2180,6 @@ _outQuery(StringInfo str, const Query *node)
                appendStringInfo(str, " :utilityStmt <>");
 
        WRITE_INT_FIELD(resultRelation);
-       WRITE_NODE_FIELD(intoClause);
        WRITE_BOOL_FIELD(hasAggs);
        WRITE_BOOL_FIELD(hasWindowFuncs);
        WRITE_BOOL_FIELD(hasSubLinks);
index b9258ad70e8effa1f1797272384facd27c295737..9b579560c5e855aaaa89074eb506c9430e9db855 100644 (file)
@@ -198,7 +198,6 @@ _readQuery(void)
        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);
index 8bbe97713bb13cc4d3c6fba638c2d771ee5901b3..6b0541b9b59346d5166e88ba78e5b5172ac6edaf 100644 (file)
@@ -233,7 +233,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
        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;
index 69396694aaa9df0edbc361ede98b3f77db6724dc..9e347ce7360c8d8d41124dd6e20488b3c267dc12 100644 (file)
@@ -22,6 +22,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
+#include "tcop/utility.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
@@ -1887,16 +1888,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
                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;
                }
 
index b64db1e1c0659ea9b6af25327f689b083de845e6..f30f02f266d2596942d89b3e3c7b8d4d6ff7fe2c 100644 (file)
@@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query)
         * are complex.
         */
        if (query->commandType != CMD_SELECT ||
-               query->intoClause ||
                query->setOperations ||
                query->hasAggs ||
                query->hasWindowFuncs ||
index 2fd795b45929014257930edd4af620e7e8d7a5a7..c1b2f1db11adc7785db84b6e9da8cebae753ee3a 100644 (file)
@@ -1136,8 +1136,7 @@ is_simple_subquery(Query *subquery)
         */
        if (!IsA(subquery, Query) ||
                subquery->commandType != CMD_SELECT ||
-               subquery->utilityStmt != NULL ||
-               subquery->intoClause != NULL)
+               subquery->utilityStmt != NULL)
                elog(ERROR, "subquery is bogus");
 
        /*
@@ -1223,8 +1222,7 @@ is_simple_union_all(Query *subquery)
        /* 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? */
index cd3da46bc5e27e29c2eb04f14745ed9777316402..b14ae2e3670f2f41a03c1688a4186beb1b3dd955 100644 (file)
@@ -4158,7 +4158,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
        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);
 
@@ -4168,7 +4168,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
        if (!IsA(querytree, Query) ||
                querytree->commandType != CMD_SELECT ||
                querytree->utilityStmt ||
-               querytree->intoClause ||
                querytree->hasAggs ||
                querytree->hasWindowFuncs ||
                querytree->hasSubLinks ||
@@ -4678,12 +4677,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
        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;
 
        /*
index b187b03666a30ff40825a34c240a08a13f7789c2..3329e9c964548e40a84397e8c26135f1cae74c58 100644 (file)
@@ -10,8 +10,8 @@
  * 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
@@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
                                                   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);
 
@@ -91,7 +93,7 @@ parse_analyze(Node *parseTree, const char *sourceText,
        if (numParams > 0)
                parse_fixed_parameters(pstate, paramTypes, numParams);
 
-       query = transformStmt(pstate, parseTree);
+       query = transformTopLevelStmt(pstate, parseTree);
 
        free_parsestate(pstate);
 
@@ -118,7 +120,7 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
 
        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);
@@ -151,8 +153,52 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
 }
 
 /*
- * 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)
@@ -202,6 +248,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
                                                                                  (ExplainStmt *) parseTree);
                        break;
 
+               case T_CreateTableAsStmt:
+                       result = transformCreateTableAsStmt(pstate,
+                                                                                       (CreateTableAsStmt *) parseTree);
+                       break;
+
                default:
 
                        /*
@@ -258,6 +309,7 @@ analyze_requires_snapshot(Node *parseTree)
                        break;
 
                case T_ExplainStmt:
+               case T_CreateTableAsStmt:
                        /* yes, because we must analyze the contained statement */
                        result = true;
                        break;
@@ -459,17 +511,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
                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
@@ -539,6 +585,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                int                     sublist_length = -1;
                int                     i;
 
+               Assert(selectStmt->intoClause == NULL);
+
                foreach(lc, selectStmt->valuesLists)
                {
                        List       *sublist = (List *) lfirst(lc);
@@ -653,6 +701,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
                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,
@@ -886,6 +935,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
                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;
 
@@ -963,9 +1020,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
                                                                                                   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);
 
@@ -1013,6 +1067,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 
        /* 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);
@@ -1185,9 +1240,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
                                (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
@@ -1286,21 +1338,27 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
        }
 
        /*
-        * 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!
         */
@@ -2107,14 +2165,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
                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
@@ -2170,6 +2220,29 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
 {
        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);
 
index feb28a41720f20d44ee3eb8fb666772203b1ed9d..3827e2e1add11a0387a9dc7ca11439404e76196c 100644 (file)
@@ -124,7 +124,6 @@ static void check_qualified_name(List *names, core_yyscan_t yyscanner);
 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,
@@ -3044,31 +3043,27 @@ ExistingIndex:   USING INDEX index_name                         { $$ = $3; }
                ;
 
 
-/*
- * 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;
                                }
                ;
 
@@ -8285,20 +8280,22 @@ ExecuteStmt: EXECUTE name execute_param_clause
                                        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;
                                }
                ;
 
@@ -12870,18 +12867,6 @@ extractArgTypes(List *parameters)
        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.
  *
index 7f4da921b2f0ff906057c43e57066d728548feee..3a23cddd3378e31651f064b0765de0429c8b3907 100644 (file)
@@ -495,12 +495,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
                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
index c0c624010379fe8a317626a6882c1043b82a69ab..2a7d4cd07244054a9e3cea3554712eb133662384 100644 (file)
@@ -253,13 +253,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
        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.
index d22d8a12bac802d45479bcdb1e810996d4cad916..973265bcb0b1af7b3411b3f1a54aee27d790297c 100644 (file)
@@ -1408,12 +1408,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
                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;
 
index 645182dbfa4668450b35e3ba73bd2f3167e1eb52..6e5633dcdb5200c5bce73936c6938d0a11b36908 100644 (file)
@@ -323,8 +323,7 @@ DefineQueryRewrite(char *rulename,
                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")));
index 943b6c141cadc03eaad7976a265eba88e5bb772d..c6ab54aa3bb5625f93f21d6afda3813edc3344f6 100644 (file)
@@ -31,7 +31,7 @@
 #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"
@@ -118,7 +118,7 @@ CreateDestReceiver(CommandDest dest)
                        return CreateTuplestoreDestReceiver();
 
                case DestIntoRel:
-                       return CreateIntoRelDestReceiver();
+                       return CreateIntoRelDestReceiver(NULL);
 
                case DestCopyOut:
                        return CreateCopyDestReceiver();
index 397c0734c2900501b443bf16f2a7296c2d09aa88..14ca7681392e93a6679cadf7a9c36108663f48e8 100644 (file)
@@ -624,7 +624,7 @@ pg_analyze_and_rewrite_params(Node *parsetree,
        pstate->p_sourcetext = query_string;
        (*parserSetup) (pstate, parserSetupArg);
 
-       query = transformStmt(pstate, parsetree);
+       query = transformTopLevelStmt(pstate, parsetree);
 
        free_parsestate(pstate);
 
@@ -975,7 +975,7 @@ exec_simple_query(const char *query_string)
                 * 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)
@@ -1709,7 +1709,7 @@ exec_bind_message(StringInfo input_message)
         * 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)
index 42a0fb0f1f26e400b7949b0be0a388d6b290d38d..d0db7ad62c23bebc72427bef6742f8da6787d50c 100644 (file)
@@ -260,8 +260,7 @@ ChoosePortalStrategy(List *stmts)
                        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;
@@ -285,8 +284,7 @@ ChoosePortalStrategy(List *stmts)
                        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;
@@ -395,8 +393,7 @@ FetchStatementTargetList(Node *stmt)
                else
                {
                        if (query->commandType == CMD_SELECT &&
-                               query->utilityStmt == NULL &&
-                               query->intoClause == NULL)
+                               query->utilityStmt == NULL)
                                return query->targetList;
                        if (query->returningList)
                                return query->returningList;
@@ -408,8 +405,7 @@ FetchStatementTargetList(Node *stmt)
                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;
@@ -430,7 +426,6 @@ FetchStatementTargetList(Node *stmt)
                ExecuteStmt *estmt = (ExecuteStmt *) stmt;
                PreparedStatement *entry;
 
-               Assert(!estmt->into);
                entry = FetchPreparedStatement(estmt->name, true);
                return FetchPreparedStatementTargetList(entry);
        }
@@ -442,9 +437,15 @@ FetchStatementTargetList(Node *stmt)
  *             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
@@ -456,14 +457,15 @@ FetchStatementTargetList(Node *stmt)
  * 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);
@@ -517,17 +519,18 @@ PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot)
 
                                /*
                                 * 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
index 5b81c0bbedcf324f6832e856c8bc8d15e2f26c24..ea2a6c6a0822c5860278f9371dd18e945d7ae116 100644 (file)
@@ -29,6 +29,7 @@
 #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"
@@ -127,9 +128,7 @@ CommandIsReadOnly(Node *parsetree)
                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 */
@@ -198,6 +197,7 @@ check_xact_readonly(Node *parsetree)
                case T_CreateSchemaStmt:
                case T_CreateSeqStmt:
                case T_CreateStmt:
+               case T_CreateTableAsStmt:
                case T_CreateTableSpaceStmt:
                case T_CreateTrigStmt:
                case T_CompositeTypeStmt:
@@ -673,7 +673,8 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_ExecuteStmt:
-                       ExecuteQuery((ExecuteStmt *) parsetree, queryString, params,
+                       ExecuteQuery((ExecuteStmt *) parsetree, NULL,
+                                                queryString, params,
                                                 dest, completionTag);
                        break;
 
@@ -1036,6 +1037,11 @@ standard_ProcessUtility(Node *parsetree,
                        ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
                        break;
 
+               case T_CreateTableAsStmt:
+                       ExecCreateTableAs((CreateTableAsStmt *) parsetree,
+                                                         queryString, params, completionTag);
+                       break;
+
                case T_VariableSetStmt:
                        ExecSetVariableStmt((VariableSetStmt *) parsetree);
                        break;
@@ -1230,8 +1236,6 @@ UtilityReturnsTuples(Node *parsetree)
                                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 */
@@ -1282,8 +1286,6 @@ UtilityTupleDescriptor(Node *parsetree)
                                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 */
@@ -1317,9 +1319,8 @@ QueryReturnsTuples(Query *parsetree)
        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:
@@ -1341,6 +1342,37 @@ QueryReturnsTuples(Query *parsetree)
 #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
@@ -1907,6 +1939,13 @@ CreateCommandTag(Node *parsetree)
                        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)
                        {
@@ -2060,8 +2099,6 @@ CreateCommandTag(Node *parsetree)
                                                        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 */
@@ -2110,8 +2147,6 @@ CreateCommandTag(Node *parsetree)
                                                        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 */
@@ -2179,7 +2214,7 @@ GetCommandLogLevel(Node *parsetree)
 
                case T_SelectStmt:
                        if (((SelectStmt *) parsetree)->intoClause)
-                               lev = LOGSTMT_DDL;              /* CREATE AS, SELECT INTO */
+                               lev = LOGSTMT_DDL;              /* SELECT INTO */
                        else
                                lev = LOGSTMT_ALL;
                        break;
@@ -2429,6 +2464,10 @@ GetCommandLogLevel(Node *parsetree)
                        }
                        break;
 
+               case T_CreateTableAsStmt:
+                       lev = LOGSTMT_DDL;
+                       break;
+
                case T_VariableSetStmt:
                        lev = LOGSTMT_ALL;
                        break;
@@ -2529,10 +2568,7 @@ GetCommandLogLevel(Node *parsetree)
                                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:
@@ -2558,10 +2594,7 @@ GetCommandLogLevel(Node *parsetree)
                                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:
index 7e66645b2b61d18552bb30ae5487f42a555fad50..6292f8dc6c99a55e81b4bdd781acdbd1ff96e3cb 100644 (file)
@@ -1232,20 +1232,16 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
                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;
                }
 
@@ -1304,13 +1300,10 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
 
                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;
                }
 
@@ -1648,9 +1641,9 @@ ResetPlanCache(void)
                 * 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)
                {
@@ -1658,12 +1651,13 @@ ResetPlanCache(void)
 
                        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;
                        }
                }
index 59fd53d2c5fd73a246ac702f58172347a73b4a48..5d896bd11ae019aff70867379e16d06fddbe7d7d 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201203111
+#define CATALOG_VERSION_NO     201203191
 
 #endif
diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h
new file mode 100644 (file)
index 0000000..ed65ccd
--- /dev/null
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 */
index a987c4312420a2bbbf277b58e7266f03c989e85d..e4e98bfb0435ad05b9fc2f515f5d0ad93bcd1479 100644 (file)
@@ -42,6 +42,7 @@ typedef struct ExplainState
 
 /* Hook for plugins to get control in ExplainOneQuery() */
 typedef void (*ExplainOneQuery_hook_type) (Query *query,
+                                                                                  IntoClause *into,
                                                                                                           ExplainState *es,
                                                                                                         const char *queryString,
                                                                                                           ParamListInfo params);
@@ -59,10 +60,12 @@ extern void ExplainInitState(ExplainState *es);
 
 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);
index 472a357c2998893cdc6c8e678760c5c67f821cf6..f688be77a322d72e670194c4089e613a6bf9d736 100644 (file)
@@ -35,11 +35,12 @@ typedef struct
 
 /* 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 */
@@ -52,6 +53,6 @@ extern void DropPreparedStatement(const char *stmt_name, bool showError);
 extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
 extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
 
-void           DropAllPreparedStatements(void);
+extern void DropAllPreparedStatements(void);
 
 #endif   /* PREPARE_H */
index 7f27669571243f111105e6558553e814cd6ff0d3..f5503a566341550b5d49b3c31eb0a3bb7aee9513 100644 (file)
@@ -30,8 +30,8 @@
  *
  * 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 */
 
 
 /*
@@ -204,7 +210,6 @@ extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
 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
index 5207102f6c9e79c969c0c5f2b46df4a9800ca452..b48a03b4b259043fbca8777421280823012962d1 100644 (file)
@@ -371,8 +371,6 @@ typedef struct EState
 
        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 */
index 905458fd50bfbc02f23473f11858ca10ffd2f4dc..fdcc80edbb567f981b6e558b6f14052f06f28e95 100644 (file)
@@ -303,6 +303,7 @@ typedef enum NodeTag
        T_DropdbStmt,
        T_VacuumStmt,
        T_ExplainStmt,
+       T_CreateTableAsStmt,
        T_CreateSeqStmt,
        T_AlterSeqStmt,
        T_VariableSetStmt,
index ab5563997d409d40ceec4d0957438bbc89a17996..07a1ab75502a5521658a88d239c91b02d4773abd 100644 (file)
@@ -84,7 +84,7 @@ typedef uint32 AclMode;                       /* a bitmask of privilege bits */
 
 /*
  * 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
@@ -111,8 +111,6 @@ typedef struct Query
        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 */
@@ -1009,7 +1007,7 @@ typedef struct SelectStmt
         */
        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 */
@@ -2395,6 +2393,25 @@ typedef struct ExplainStmt
        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
  * ----------------------
@@ -2509,7 +2526,6 @@ typedef struct ExecuteStmt
 {
        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;
 
index e6bb3239f4214c26aa1d70d4ca4ac50f63148ad5..c7c1a154fc47df9c137754359a3522ee09dfc41f 100644 (file)
@@ -54,8 +54,6 @@ typedef struct PlannedStmt
 
        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 */
index b8987db214b731653d11c7af8882aae7615af3ee..8367db8b8c58f7ddedc9ddca39e73a83d8fe9e7c 100644 (file)
@@ -25,6 +25,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
 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);
index 98b825a1067a8ecd0656413626ed58028120e611..22aad2e96cc230d2c1cdfc7a44332f77b36b942e 100644 (file)
@@ -28,7 +28,7 @@ extern List *FetchPortalTargetList(Portal portal);
 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);
index 14f0fa8e9dc2741ffb41bdbcf57071b02799e447..54190b2f6ce380d5ec8da80c8e8a664a0338b482 100644 (file)
@@ -34,6 +34,8 @@ extern bool UtilityReturnsTuples(Node *parsetree);
 
 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
 
+extern Query *UtilityContainsQuery(Node *parsetree);
+
 extern const char *CreateCommandTag(Node *parsetree);
 
 extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
index 0b126a3f4ac0e16e4ae19b8507c5c1352842a7e2..09c86aa4c676f93bae92fcad65fdb41f5c636379 100644 (file)
@@ -3291,24 +3291,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
                         * 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:
@@ -5759,7 +5749,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
         */
        if (!IsA(query, Query))
                return;
-       if (query->commandType != CMD_SELECT || query->intoClause)
+       if (query->commandType != CMD_SELECT)
                return;
        if (query->rtable != NIL)
                return;
@@ -5833,7 +5823,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
         */
        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))
index c8327f677ae34987de1ff21062162e4ed3ed1f5c..9d3f04758e91c66295b47d4a23624bb963ab748b 100644 (file)
@@ -75,3 +75,22 @@ SELECT * FROM created_table;
 (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;
+                                    ^
index e9d3908b1ab53d69fc833774a82375902b548610..6981692da9e3f5bb93e7ea5e2f3f79be85c5a64c 100644 (file)
@@ -139,7 +139,7 @@ SELECT * FROM writetest, temptest; -- ok
 (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;
index 09d210bc6f98bbe998730a53f14d46a277b8d00b..4d1cc86556b4a9899c84ca93863d695c971f199e 100644 (file)
@@ -67,3 +67,12 @@ SELECT make_table();
 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;