0001-Transform-CREATE-TABLE-AS-SELECT-INTO-into-a-utility.patch
text/x-patch
Filename: 0001-Transform-CREATE-TABLE-AS-SELECT-INTO-into-a-utility.patch
Type: text/x-patch
Part: 0
Message:
Re: Command Triggers
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Transform CREATE TABLE AS/SELECT INTO into a utility statement
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam.c | 7 | 2 |
| src/backend/commands/copy.c | 3 | 1 |
| src/backend/commands/prepare.c | 6 | 17 |
| src/backend/commands/tablecmds.c | 225 | 0 |
| src/backend/commands/view.c | 3 | 0 |
| src/backend/executor/execMain.c | 14 | 376 |
| src/backend/executor/execUtils.c | 0 | 2 |
| src/backend/executor/functions.c | 1 | 3 |
| src/backend/executor/spi.c | 7 | 4 |
| src/backend/nodes/copyfuncs.c | 15 | 3 |
| src/backend/nodes/equalfuncs.c | 14 | 2 |
| src/backend/nodes/outfuncs.c | 1 | 3 |
| src/backend/nodes/readfuncs.c | 0 | 1 |
| src/backend/optimizer/plan/planner.c | 0 | 1 |
| src/backend/optimizer/plan/subselect.c | 0 | 1 |
| src/backend/optimizer/prep/prepjointree.c | 2 | 4 |
| src/backend/optimizer/util/clauses.c | 1 | 3 |
| src/backend/parser/analyze.c | 57 | 29 |
| src/backend/parser/gram.y | 23 | 4 |
| src/backend/parser/parse_clause.c | 0 | 6 |
| src/backend/parser/parse_cte.c | 3 | 0 |
| src/backend/parser/parse_expr.c | 4 | 0 |
| src/backend/parser/parser.c | 38 | 0 |
| src/backend/rewrite/rewriteDefine.c | 1 | 2 |
| src/backend/tcop/pquery.c | 4 | 9 |
| src/backend/tcop/utility.c | 21 | 25 |
| src/include/access/htup.h | 1 | 1 |
| src/include/commands/tablecmds.h | 4 | 0 |
| src/include/nodes/execnodes.h | 3 | 0 |
| src/include/nodes/nodes.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 15 | 5 |
| src/include/nodes/plannodes.h | 0 | 2 |
| src/pl/plpgsql/src/pl_exec.c | 8 | 19 |
| src/test/regress/expected/transactions.out | 1 | 1 |
From 334a60a213804e32d94482211e7e2824b4253b35 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sun, 4 Dec 2011 19:51:31 +0100
Subject: [PATCH] Transform CREATE TABLE AS/SELECT INTO into a utility
statement
Currently missing parts are:
* CREATE TABLE AS ... EXECUTE
* oid handling
* difference in handling of collations
* some error messages are too generic
---
src/backend/access/heap/heapam.c | 9 +-
src/backend/commands/copy.c | 4 +-
src/backend/commands/prepare.c | 23 +--
src/backend/commands/tablecmds.c | 225 ++++++++++++++++
src/backend/commands/view.c | 3 +
src/backend/executor/execMain.c | 390 +---------------------------
src/backend/executor/execUtils.c | 2 -
src/backend/executor/functions.c | 4 +-
src/backend/executor/spi.c | 11 +-
src/backend/nodes/copyfuncs.c | 18 +-
src/backend/nodes/equalfuncs.c | 16 +-
src/backend/nodes/outfuncs.c | 4 +-
src/backend/nodes/readfuncs.c | 1 -
src/backend/optimizer/plan/planner.c | 1 -
src/backend/optimizer/plan/subselect.c | 1 -
src/backend/optimizer/prep/prepjointree.c | 6 +-
src/backend/optimizer/util/clauses.c | 4 +-
src/backend/parser/analyze.c | 86 ++++--
src/backend/parser/gram.y | 27 ++-
src/backend/parser/parse_clause.c | 6 -
src/backend/parser/parse_cte.c | 3 +
src/backend/parser/parse_expr.c | 4 +
src/backend/parser/parser.c | 38 +++
src/backend/rewrite/rewriteDefine.c | 3 +-
src/backend/tcop/pquery.c | 13 +-
src/backend/tcop/utility.c | 46 ++--
src/include/access/htup.h | 2 +-
src/include/commands/tablecmds.h | 4 +
src/include/nodes/execnodes.h | 3 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 20 +-
src/include/nodes/plannodes.h | 2 -
src/pl/plpgsql/src/pl_exec.c | 27 +--
src/test/regress/expected/transactions.out | 2 +-
34 files changed, 483 insertions(+), 526 deletions(-)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 7b27c23..06d5d1d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -2044,7 +2044,10 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
else
{
/* check there is not space for an OID */
- Assert(!(tup->t_data->t_infomask & HEAP_HASOID));
+ //FIXME: CTAS move
+ if(tup->t_data->t_infomask & HEAP_HASOID){
+ elog(ERROR, "Assert(!(tup->t_data->t_infomask & HEAP_HASOID))");
+ }
}
tup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
@@ -2919,7 +2922,9 @@ l2:
else
{
/* check there is not space for an OID */
- Assert(!(newtup->t_data->t_infomask & HEAP_HASOID));
+ if(newtup->t_data->t_infomask & HEAP_HASOID){
+ elog(ERROR, "Assert(!(newtup->t_data->t_infomask & HEAP_HASOID))");
+ }
}
newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9c994ef..09dbb8d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1211,12 +1211,14 @@ BeginCopy(bool is_from,
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
+#if 0
+//FIXME: improve error message at upper layer
/* Query mustn't use INTO, either */
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
-
+#endif
/* plan the query */
plan = planner(query, 0, NULL);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index a949215..a423628 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -222,6 +222,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
entry->plansource->query_string);
+#if 0
+ //FIXME: Reimplement this after CTAS move
/*
* 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
@@ -263,10 +265,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
}
else
{
- /* Replan if needed, and increment plan refcount for portal */
- cplan = GetCachedPlan(entry->plansource, paramLI, false);
- plan_list = cplan->stmt_list;
}
+#endif
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = GetCachedPlan(entry->plansource, paramLI, false);
+ plan_list = cplan->stmt_list;
PortalDefineQuery(portal,
NULL,
@@ -666,20 +669,6 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
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);
}
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c4622c0..1d98e69 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -68,6 +68,7 @@
#include "parser/parser.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
+#include "tcop/tcopprot.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/lock.h"
@@ -195,6 +196,18 @@ struct dropmsgstrings
const char *drophint_msg;
};
+/*
+ * Support structure for CTAS
+ */
+typedef struct
+{
+ DestReceiver pub; /* publicly-known function pointers */
+ EState *estate; /* EState we are working with */
+ Relation rel; /* Relation to write to */
+ int hi_options; /* heap_insert performance options */
+ BulkInsertState bistate; /* bulk insert state */
+} DR_intorel;
+
static const struct dropmsgstrings dropmsgstringarray[] = {
{RELKIND_RELATION,
ERRCODE_UNDEFINED_TABLE,
@@ -640,6 +653,129 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
return relationId;
}
+void
+CreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+ ParamListInfo params, DestReceiver *dest){
+ IntoClause *into = stmt->into;
+ Query *query;
+ List *rewritten;
+ PlannedStmt *plan;
+ QueryDesc *queryDesc;
+ Relation intoRelationDesc;
+ CreateStmt* create;
+ int natt;
+ Oid intoRelationId;
+ ListCell* lc;
+
+ Assert(IsA(stmt->query, Query));
+ query = (Query*)stmt->query;
+
+ if(query->commandType == CMD_SELECT){
+ }
+ else if(query->commandType == CMD_UTILITY &&
+ nodeTag(query->utilityStmt) == T_ExecuteStmt){
+ elog(ERROR, "CREATE TABLE ... AS EXECUTE is unimplemented right now");
+ }
+ else{
+ elog(ERROR, "unsupported command for CREATE TABLE AS");
+ }
+
+ rewritten = QueryRewrite((Query *) copyObject(stmt->query));
+ if(!rewritten)
+ elog(ERROR, "CREATE TABLE AS/SELECT INTO query rewrote to nothing");
+
+ if(list_length(rewritten) != 1)
+ elog(ERROR, "CREATE TABLE AS/SELECT INTO query rewrote to more than one query");
+
+ plan = pg_plan_query(lfirst(list_head(rewritten)), 0, params);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees
+ * results of any previously executed queries.
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ queryDesc = CreateQueryDesc(plan, queryString,
+ GetActiveSnapshot(), InvalidSnapshot,
+ CreateDestReceiver(DestIntoRel), params,
+ false);
+
+ /*
+ * call ExecutorStart to prepare the plan for execution. Only
+ * after this we have enough information to actually create a
+ * target relation
+ */
+ ExecutorStart(queryDesc, 0);
+
+
+ /*
+ * create the target relation
+ */
+ create = makeNode(CreateStmt);
+ create->relation = into->rel;
+ create->options = into->options;
+ create->oncommit = into->onCommit;
+ create->tablespacename = into->tableSpaceName;
+
+ if (list_length(into->colNames) > queryDesc->tupDesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("CREATE TABLE AS specifies too many column names")));
+ lc = list_head(into->colNames);
+ for(natt = 0; natt < queryDesc->tupDesc->natts; natt++){
+ ColumnDef *col = makeNode(ColumnDef);
+ TypeName *type = makeNode(TypeName);
+
+ Form_pg_attribute attribute = queryDesc->tupDesc->attrs[natt];
+
+ /*
+ * 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.)
+ */
+ if(!lc)
+ col->colname = NameStr(attribute->attname);
+ else{
+ col->colname = strVal(lfirst(lc));
+ lc = lnext(lc);
+ }
+
+ col->collOid = attribute->attcollation;
+
+ type->typeOid = attribute->atttypid;
+ type->typemod = attribute->atttypmod;
+ col->typeName= type;
+ create->tableElts = lappend(create->tableElts, col);
+ }
+
+ //setup target
+ intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
+ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+
+ if(!into->skipData){
+ DR_intorel *myState = (DR_intorel *) queryDesc->dest;
+ /* setup the target of the DestIntoRel receiver */
+ myState->estate = queryDesc->estate;
+ myState->rel = intoRelationDesc;
+
+ /* run the plan */
+ ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+
+ }
+
+ /* run cleanup too */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ /* close rel, but keep lock until commit */
+ heap_close(intoRelationDesc, NoLock);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+}
+
/*
* Emit the right error or warning message for a "DROP" command issued on a
* non-existent relation
@@ -9771,3 +9907,92 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
}
}
}
+
+/*
+ * 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.
+ */
+
+/*
+ * intorel_startup --- executor startup
+ */
+static void
+intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ DR_intorel *myState = (DR_intorel *) self;
+ myState->bistate = GetBulkInsertState();
+}
+
+/*
+ * 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)
+{
+ DR_intorel *myState = (DR_intorel *) self;
+
+ FreeBulkInsertState(myState->bistate);
+ if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
+ heap_sync(myState->rel);
+}
+
+/*
+ * intorel_destroy --- release DestReceiver object
+ */
+static void
+intorel_destroy(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b238199..594bcce 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -441,10 +441,13 @@ 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 0
+//FIXME: better error message on toplevel
if (viewParse->intoClause != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("views must not contain SELECT INTO")));
+#endif
if (viewParse->hasModifyingCTE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d19e097..bc03ec5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -87,12 +87,14 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
+#if 0
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);
+#endif
/* end of local decls */
@@ -171,11 +173,10 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_SELECT:
/*
- * SELECT INTO, SELECT FOR UPDATE/SHARE and modifying CTEs need to
+ * 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);
@@ -307,13 +308,6 @@ standard_ExecutorRun(QueryDesc *queryDesc,
(*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
*/
if (!ScanDirectionIsNoMovement(direction))
@@ -448,12 +442,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);
@@ -703,15 +691,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)
{
@@ -861,18 +840,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
/*
- * 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.
*/
estate->es_tupleTable = NIL;
@@ -923,9 +890,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);
@@ -965,16 +930,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);
}
/*
@@ -1227,7 +1182,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 or UPDATE
* 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
@@ -1252,7 +1207,7 @@ 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
+ * FIXME: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().
*/
@@ -1271,13 +1226,17 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids)
return true;
}
}
-
+#if 0
+/* FIXME: We need to get to know whether our result relation needs
+ * oids. This currently the only place where the executor retains
+ * knowledge about SELECT INTO
+ */
if (planstate->state->es_select_into)
{
*hasoids = planstate->state->es_into_oids;
return true;
}
-
+#endif
return false;
}
@@ -2224,8 +2183,7 @@ 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 */
/*
@@ -2367,323 +2325,3 @@ EvalPlanQualEnd(EPQState *epqstate)
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 */
- 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")));
-
- /*
- * 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
- */
- intoName = into->rel->relname;
- namespaceId = RangeVarGetAndCheckCreationNamespace(into->rel);
- RangeVarAdjustRelationPersistence(into->rel, namespaceId);
-
- /*
- * 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
- */
- queryDesc->dest = CreateDestReceiver(DestIntoRel);
- myState = (DR_intorel *) queryDesc->dest;
- Assert(myState->pub.mydest == DestIntoRel);
- myState->estate = estate;
- myState->rel = intoRelationDesc;
-
- /*
- * 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 */
- if (myState && myState->pub.mydest == DestIntoRel && myState->rel)
- {
- 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;
- }
-}
-
-/*
- * 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);
-}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 65591e2..acab607 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -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;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 45ca5ec..30185f2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -349,7 +349,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;
}
@@ -1307,8 +1306,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;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 688279c..b3d113d 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1423,6 +1423,7 @@ SPI_getargcount(SPIPlanPtr plan)
* INSERT ... RETURNING, but not SELECT ... INTO). In essence,
* the result indicates if the command can be used with SPI_cursor_open
*
+ * FIXME: adjust comment
* Parameters
* plan: A plan previously prepared using SPI_prepare
*/
@@ -1921,7 +1922,11 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced -
_SPI_current->tuptable->free;
- res = SPI_OK_UTILITY;
+ if(nodeTag(stmt) == T_CreateTableAsStmt &&
+ ((CreateTableAsStmt*)stmt)->is_select_into)
+ res = SPI_OK_SELINTO;
+ else
+ res = SPI_OK_UTILITY;
}
/*
@@ -2046,9 +2051,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;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c70a5bd..d8e7eb3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -85,7 +85,6 @@ _copyPlannedStmt(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);
@@ -2417,7 +2416,6 @@ _copyQuery(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);
@@ -3201,6 +3199,18 @@ _copyExplainStmt(ExplainStmt *from)
return newnode;
}
+static CreateTableAsStmt *
+_copyCreateTableAsStmt(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(CreateSeqStmt *from)
{
@@ -3608,7 +3618,6 @@ _copyExecuteStmt(ExecuteStmt *from)
ExecuteStmt *newnode = makeNode(ExecuteStmt);
COPY_STRING_FIELD(name);
- COPY_NODE_FIELD(into);
COPY_NODE_FIELD(params);
return newnode;
@@ -4243,6 +4252,9 @@ copyObject(void *from)
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
+ case T_CreateTableAsStmt:
+ retval = _copyCreateTableAsStmt(from);
+ break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f490a7a..e090e38 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -900,7 +900,6 @@ _equalQuery(Query *a, 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);
@@ -1560,6 +1559,17 @@ _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
return true;
}
+
+static bool
+_equalCreateTableAsStmt(CreateTableAsStmt *a, CreateTableAsStmt *b)
+{
+ COMPARE_NODE_FIELD(query);
+ COMPARE_NODE_FIELD(into);
+ COMPARE_SCALAR_FIELD(is_select_into);
+
+ return true;
+}
+
static bool
_equalCreateSeqStmt(CreateSeqStmt *a, CreateSeqStmt *b)
{
@@ -1903,7 +1913,6 @@ static bool
_equalExecuteStmt(ExecuteStmt *a, ExecuteStmt *b)
{
COMPARE_STRING_FIELD(name);
- COMPARE_NODE_FIELD(into);
COMPARE_NODE_FIELD(params);
return true;
@@ -2786,6 +2795,9 @@ equal(void *a, 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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 31af47f..997f5e2 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -248,7 +248,6 @@ _outPlannedStmt(StringInfo str, 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);
@@ -2197,7 +2196,6 @@ _outQuery(StringInfo str, 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);
@@ -2813,7 +2811,7 @@ _outNode(StringInfo str, void *obj)
case T_RangeVar:
_outRangeVar(str, obj);
break;
- case T_IntoClause:
+ case T_IntoClause:/*XXX: This could be removed but dim would need to add it right back*/
_outIntoClause(str, obj);
break;
case T_Var:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3de20ad..8a35c4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5c18b72..93b7773 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -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;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index e396520..a995fdf 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query)
* are complex.
*/
if (query->commandType != CMD_SELECT ||
- query->intoClause ||
query->setOperations ||
query->hasAggs ||
query->hasWindowFuncs ||
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 8bb011b..f71a988 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1047,8 +1047,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");
/*
@@ -1134,8 +1133,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? */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ad02950..7422387 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4028,7 +4028,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 ||
@@ -4542,8 +4541,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
*/
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
- querytree->utilityStmt ||
- querytree->intoClause)
+ querytree->utilityStmt)
goto fail;
/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index dae5478..10be4c9 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -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);
@@ -202,6 +204,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(ExplainStmt *) parseTree);
break;
+ case T_CreateTableAsStmt:
+ result = transformCreateTableAsStmt(pstate,
+ (CreateTableAsStmt *) parseTree);
+ break;
+
default:
/*
@@ -459,17 +466,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
free_parsestate(sub_pstate);
- /* The grammar should have produced a SELECT, but it might have INTO */
+ /* FIXME: comment: The grammar should have produced a SELECT, but it might have INTO */
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
@@ -876,6 +877,18 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
Node *qual;
ListCell *l;
+ /*
+ * Validity-check whether we got called from somewhere where
+ * ... INTO was not allowed
+ */
+ if (stmt->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
+
qry->commandType = CMD_SELECT;
/* process the WITH clause independently of all else */
@@ -963,8 +976,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);
@@ -1021,6 +1032,17 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
Assert(stmt->windowClause == NIL);
Assert(stmt->op == SETOP_NONE);
+ /*
+ * Validity-check whether we got called from somewhere where
+ * ... INTO was not allowed
+ */
+ if (stmt->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
/* process the WITH clause independently of all else */
if (stmt->withClause)
{
@@ -1185,9 +1207,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
@@ -1253,7 +1272,6 @@ static Query *
transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
{
Query *qry = makeNode(Query);
- SelectStmt *leftmostSelect;
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
@@ -1286,20 +1304,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
}
/*
- * Find leftmost leaf SelectStmt; extract the one-time-only items from it
- * and from the top-level node.
- */
- 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;
-
- /*
* These are not one-time, exactly, but we want to process them here and
* not let transformSetOperationTree() see them --- else it'll just
* recurse right back here!
@@ -1330,7 +1334,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
qry->setOperations = (Node *) sostmt;
/*
- * Re-find leftmost SELECT (now it's a sub-query in rangetable)
+ * Find leftmost SELECT (it's a sub-query in rangetable)
*/
node = sostmt->larg;
while (node && IsA(node, SetOperationStmt))
@@ -2107,6 +2111,8 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
result->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
+#if 0
+FIXME
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (result->intoClause)
ereport(ERROR,
@@ -2114,7 +2120,7 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
errmsg("DECLARE CURSOR cannot specify INTO"),
parser_errposition(pstate,
exprLocation((Node *) result->intoClause))));
-
+#endif
/*
* We also disallow data-modifying WITH in a cursor. (This could be
* allowed, but the semantics of when the updates occur might be
@@ -2183,6 +2189,28 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
/*
+ * transformCreateTableAsStmt -
+ * transform an CREATE TABLE AS/SELECT ... INTO Statement
+ *
+ */
+static Query *
+transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
+{
+ Query *result;
+
+ /* transform contained query */
+ stmt->query = (Node *) transformStmt(pstate, stmt->query);
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+
+/*
* Check for features that are not supported together with FOR UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2a497d1..670832e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3010,23 +3010,37 @@ ExistingIndex: USING INDEX index_name { $$ = $3; }
CreateAsStmt:
CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data
{
+ SelectStmt *n;
+ CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+ ctas->into = $4;
+ ctas->query = $6;
+ ctas->is_select_into = false;
+ $4->skipData = !($7);
+ $4->rel->relpersistence = $2;
+ $4->skipData = !($7);
+ $$ = (Node*)ctas;
+
/*
* 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);
+ 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))));
+#if 0
+//FIXME
+
n->intoClause = $4;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($7);
$$ = $6;
+#endif
}
;
@@ -7994,20 +8008,25 @@ 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
{
ExecuteStmt *n = makeNode(ExecuteStmt);
+ CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+
n->name = $7;
n->params = $8;
- n->into = $4;
+
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($9);
- $$ = (Node *) n;
+
+ ctas->into = $4;
+ ctas->query = (Node*)n;
+ ctas->is_select_into = false;
+ $$ = (Node *) ctas;
}
;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e8177bc..f834c43 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -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
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index ec6afd8..e2adf0b 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -253,12 +253,15 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
if (query->utilityStmt != NULL)
elog(ERROR, "unexpected utility statement in WITH");
+#if 0
+//FIXME: recheck that this cannot happen
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery in WITH cannot have SELECT INTO"),
parser_errposition(pstate,
exprLocation((Node *) query->intoClause))));
+#endif
/*
* We disallow data-modifying WITH except at the top level of a query,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 75236c7..5597961 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1408,12 +1408,16 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in SubLink");
+
+#if 0
+ //FIXME: I don't think we can get here in a problematic case, recheck
if (qtree->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery cannot have SELECT INTO"),
parser_errposition(pstate,
exprLocation((Node *) qtree->intoClause))));
+#endif
sublink->subselect = (Node *) qtree;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index e389208..de28d84 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -37,6 +37,7 @@ raw_parser(const char *str)
core_yyscan_t yyscanner;
base_yy_extra_type yyextra;
int yyresult;
+ ListCell *c;
/* initialize the flex scanner */
yyscanner = scanner_init(str, &yyextra.core_yy_extra,
@@ -57,6 +58,43 @@ raw_parser(const char *str)
if (yyresult) /* error */
return NIL;
+ /*
+ * Some things are rather hard to properly diagnose in grammar
+ * without complicating/duplicating it too much. So we do some
+ * postprocessing here.
+ */
+ foreach(c, yyextra.parsetree){
+ switch(nodeTag(lfirst(c))){
+ /*
+ * The grammar currently doesn't disambiguate between
+ * SELECT and SELECT ... INTO. Do that now.
+ */
+ case T_SelectStmt:
+ {
+ SelectStmt* s = (SelectStmt*)lfirst(c);
+ SelectStmt* first = s;
+ while (first && first->op != SETOP_NONE)
+ first = first->larg;
+ Assert(first && IsA(first, SelectStmt) && first->larg == NULL);
+ if(first->intoClause){
+ CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+ ctas->into = first->intoClause;
+ ctas->query = (Node*)s;
+ ctas->is_select_into = true;
+ /*
+ * this way everyone can complain if this is set
+ * without further checks because it shall never
+ * be set but here.
+ */
+ first->intoClause = NULL;
+ lfirst(c) = ctas;
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ }
return yyextra.parsetree;
}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 17db70e..e359e9a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -324,8 +324,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")));
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 466727b..2628e31 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -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);
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6f88c47..4c5be0a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -127,9 +127,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 +196,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:
@@ -1017,6 +1016,10 @@ standard_ProcessUtility(Node *parsetree,
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
+ case T_CreateTableAsStmt:
+ CreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, dest);
+ break;
+
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree);
break;
@@ -1211,8 +1214,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 */
@@ -1263,8 +1264,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 */
@@ -1299,8 +1298,7 @@ QueryReturnsTuples(Query *parsetree)
{
case CMD_SELECT:
/* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */
- if (parsetree->utilityStmt == NULL &&
- parsetree->intoClause == NULL)
+ if (parsetree->utilityStmt == NULL)
return true;
break;
case CMD_INSERT:
@@ -1888,6 +1886,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)
{
@@ -2041,8 +2046,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 */
@@ -2091,8 +2094,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 */
@@ -2159,10 +2160,7 @@ GetCommandLogLevel(Node *parsetree)
break;
case T_SelectStmt:
- if (((SelectStmt *) parsetree)->intoClause)
- lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
- else
- lev = LOGSTMT_ALL;
+ lev = LOGSTMT_ALL;
break;
/* utility statements --- same whether raw or cooked */
@@ -2410,6 +2408,10 @@ GetCommandLogLevel(Node *parsetree)
}
break;
+ case T_CreateTableAsStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
@@ -2510,10 +2512,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:
@@ -2539,10 +2538,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:
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 3ca25ac..01e3049 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -311,7 +311,7 @@ do { \
#define HeapTupleHeaderSetOid(tup, oid) \
do { \
- Assert((tup)->t_infomask & HEAP_HASOID); \
+ if(!((tup)->t_infomask & HEAP_HASOID)) elog(ERROR, "HEAP_HASOID"); \
*((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) = (oid); \
} while (0)
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 333e303..bac53ac 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -15,6 +15,7 @@
#define TABLECMDS_H
#include "access/htup.h"
+#include "executor/executor.h"
#include "nodes/parsenodes.h"
#include "storage/lock.h"
#include "utils/relcache.h"
@@ -22,6 +23,9 @@
extern Oid DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId);
+extern void CreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+ ParamListInfo params, DestReceiver *dest);
+
extern void RemoveRelations(DropStmt *drop);
extern void AlterTable(AlterTableStmt *stmt);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..7d64f6c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -370,8 +370,11 @@ typedef struct EState
int es_top_eflags; /* eflags passed to ExecutorStart */
int es_instrument; /* OR of InstrumentOption flags */
+#if 0
+ //FIXME: remove. We might need the oid part...
bool es_select_into; /* true if doing SELECT INTO */
bool es_into_oids; /* true to generate OIDs in SELECT INTO */
+#endif
bool es_finished; /* true when ExecutorFinish is done */
List *es_exprcontexts; /* List of ExprContexts within EState */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3a24089..3f48b3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -305,6 +305,7 @@ typedef enum NodeTag
T_DropdbStmt,
T_VacuumStmt,
T_ExplainStmt,
+ T_CreateTableAsStmt,
T_CreateSeqStmt,
T_AlterSeqStmt,
T_VariableSetStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9e277c5..6577660 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
@@ -1008,7 +1006,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 */
@@ -2373,7 +2371,7 @@ typedef struct VacuumStmt
* Explain Statement
*
* The "query" field is either a raw parse tree (SelectStmt, InsertStmt, etc)
- * or a Query node if parse analysis has been done. Note that rewriting and
+ * or a Query node if parse analysis has been done. Note that rewriting and
* planning of the query are always postponed until execution of EXPLAIN.
* ----------------------
*/
@@ -2385,6 +2383,19 @@ typedef struct ExplainStmt
} ExplainStmt;
/* ----------------------
+ * analyzing, rewriting and planning are handled as in explain
+ * statements (see comment above) only that query can only be a
+ * SelectStmt and not some other type.
+ */
+typedef struct CreateTableAsStmt
+{
+ NodeTag type;
+ IntoClause *into;
+ Node *query; /* the query (see comments above) */
+ bool is_select_into; /* plpgsql wants to disambiguate */
+} CreateTableAsStmt;
+
+/* ----------------------
* Checkpoint Statement
* ----------------------
*/
@@ -2498,7 +2509,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;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6685864..acaae3e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -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 */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 717ad79..eee27d3 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3271,29 +3271,18 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
break;
case SPI_OK_SELINTO:
-
/*
* 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!
+ * CREATE AS should be 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:
@@ -5733,7 +5722,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;
@@ -5807,7 +5796,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))
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index f49ec0e..2da50cf 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -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;
--
1.7.6.409.ge7a85.dirty