From: Andres Freund <andres@anarazel.de>
To: pgsql-hackers@postgresql.org, Tom Lane <tgl@sss.pgh.pa.us>
Cc: Thom Brown <thom@linux.com>
Cc: Dimitri Fontaine <dimitri@2ndquadrant.fr>
Subject: Re: Command Triggers, patch v11
Date: Tue, 28 Feb 2012 00:32:26 +0100
Message-ID: <201202280032.27171.andres@anarazel.de> (raw)
In-Reply-To: <201202272037.25322.andres@anarazel.de>
References: <m24nufq466.fsf@2ndQuadrant.fr>
<CAA-aLv67=BeqLr3CukJSm2q7ZhbDnt5aiJwKDBXcyNg5g1JUug@mail.gmail.com>
<201202272037.25322.andres@anarazel.de>
On Monday, February 27, 2012 08:37:24 PM Andres Freund wrote:
> On Monday, February 27, 2012 08:30:31 PM Thom Brown wrote:
> > On 27 February 2012 19:19, Dimitri Fontaine <dimitri@2ndquadrant.fr>
wrote:
> > > Thom Brown <thom@linux.com> writes:
> > >> test=# CREATE TABLE badname AS SELECT 1::int id, 1::int a, ''::text b;
> > >> SELECT 1
> > >>
> > >> This doesn't even get picked up by ANY COMMAND.
> > >
> > > You won't believe it: CTAS is not implemented as a DDL. Andres did
> > > some work about that and sent a patch that received positive reviews by
> > > both Tom and Robert, once that's in I can easily add support for the
> > > command.
>
> I actually don't think anybody actually reviewed the patch so far. Tom and
> I discussed the implementation strategy beforehand a bit though.
>
> > > Thanks Andres :)
>
> Youre welcome. Thanks for your awesome work that actually made it necessary
> ;)
>
> > I don't see it anywhere in the commitfest. Has it been properly
> > submitted?
>
> I actually always viewed it as a part of the Dim's patch which is why I
> didn't submit it as a separate patch. Maybe that was a mistake...
>
> http://archives.postgresql.org/message-
> id/201112112346.07611.andres@anarazel.de contains the latest revision.
>
I refreshed the patch so it works again on current HEAD. Basically some
trivial fixes and dfd26f9c5f371437f243249025863ea9911aacaa. The latter doesn't
seem necessary to me after the changes, so I simply ditched it. Am I missing
something?
I noticed no new things I dislike about the patch besides what I voiced last
time round:
> I attached the - from my side - final version of the patch. I dislike two
> things about it:
> * code duplication due to error handling. Before making the error message
> for various illegal SELECT INTOs the patch actually shrank the code size...
> If anybody has a good idea to avoid duplicating that loop around SelectStmt-
ops I would be happy.
> * new executor flags to define whether oids should be returned
It would be great if somebody could take a look.
Andres
Attachments:0001-Transform-CREATE-TABLE-AS-SELECT-INTO-into-a-utility.patch (text/x-patch, 76.9K) download
From 10a3e5c26c4196cdb8ce4ba588c19872d2f1b246 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 28 Feb 2012 00:19:50 +0100
Subject: [PATCH] Transform CREATE TABLE AS/SELECT INTO into a utility
statement
This basically includes a revert of d8fb1f9adbddd1eef123d66a89a9fc0ecd96f60b
which doesn't seem to be necessary anymore now CTAS is a proper utility
statement in any incarnation.
---
src/backend/commands/copy.c | 20 +-
src/backend/commands/prepare.c | 69 +----
src/backend/commands/tablecmds.c | 335 +++++++++++++++++++++
src/backend/commands/view.c | 20 +-
src/backend/executor/execMain.c | 441 ++--------------------------
src/backend/executor/execUtils.c | 2 -
src/backend/executor/functions.c | 4 +-
src/backend/executor/spi.c | 10 +-
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 | 113 +++++---
src/backend/parser/gram.y | 28 ++-
src/backend/parser/parse_clause.c | 21 +-
src/backend/parser/parse_cte.c | 21 +-
src/backend/parser/parse_expr.c | 21 +-
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/prepare.h | 3 +
src/include/commands/tablecmds.h | 4 +
src/include/executor/executor.h | 5 +
src/include/nodes/execnodes.h | 9 +-
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/select_into.out | 19 ++
src/test/regress/expected/transactions.out | 2 +-
src/test/regress/sql/select_into.sql | 15 +
37 files changed, 720 insertions(+), 645 deletions(-)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.cindex 110480f..a3c5b4b 100644--- a/src/backend/commands/copy.c+++ b/src/backend/commands/copy.c@@ -1190,6 +1190,20 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT) WITH OIDS is not supported")));
++ /* Query mustn't use INTO, either */+ if(IsA(raw_query, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)raw_query;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("COPY (SELECT INTO) is not supported")));+ }+
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
@@ -1211,12 +1225,6 @@ BeginCopy(bool is_from,
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
- /* Query mustn't use INTO, either */- if (query->intoClause)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("COPY (SELECT INTO) is not supported")));-
/* plan the query */
plan = planner(query, 0, NULL);
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.cindex 4883abe..86e8cf9 100644--- a/src/backend/commands/prepare.c+++ b/src/backend/commands/prepare.c@@ -43,8 +43,6 @@
static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void);
-static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,- const char *queryString, EState *estate);
static Datum build_regtype_array(Oid *param_types, int num_params);
/*
@@ -177,6 +175,9 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* EXECUTE, which we might need for error reporting while processing the
* parameter expressions. The query_string that we copy from the plan
* source is that of the original PREPARE.
+ *+ * If you add additional things here you should check whether+ * CreateTableAs also needs them.
*/
void
ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
@@ -222,51 +223,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
entry->plansource->query_string);
- /*- * 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.- */- if (stmt->into)- {- 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),- errmsg("prepared statement is not a SELECT")));- pstmt = (PlannedStmt *) linitial(plan_list);- if (!IsA(pstmt, PlannedStmt) ||- pstmt->commandType != CMD_SELECT ||- pstmt->utilityStmt != NULL)- ereport(ERROR,- (errcode(ERRCODE_WRONG_OBJECT_TYPE),- errmsg("prepared statement is not a SELECT")));- pstmt->intoClause = copyObject(stmt->into);-- MemoryContextSwitchTo(oldContext);-- /* 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;- }- else- {- /* Replan if needed, and increment plan refcount for portal */- cplan = GetCachedPlan(entry->plansource, paramLI, false);- plan_list = cplan->stmt_list;- }+ /* Replan if needed, and increment plan refcount for portal */+ cplan = GetCachedPlan(entry->plansource, paramLI, false);+ plan_list = cplan->stmt_list;
PortalDefineQuery(portal,
NULL,
@@ -302,7 +261,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
* CreateQueryDesc(), which allows the executor to make use of the parameters
* during query execution.
*/
-static ParamListInfo+ParamListInfo
EvaluateParams(PreparedStatement *pstmt, List *params,
const char *queryString, EState *estate)
{
@@ -666,20 +625,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.cindex cd4490a..9a67cf5 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -44,6 +44,7 @@
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+#include "commands/prepare.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
@@ -68,6 +69,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 +197,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,
@@ -671,6 +685,235 @@ 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;+ int eflags = 0;+ EState *param_estate = NULL;+ CachedPlan *cplan = NULL;++ Assert(IsA(stmt->query, Query));+ query = (Query*)stmt->query;++ /*+ * Construct plans for the supported+ */+ if(query->commandType == CMD_SELECT){+ 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);+ }+ else if(query->commandType == CMD_UTILITY &&+ IsA(query->utilityStmt, ExecuteStmt)){+ /*+ * We cannot easily share more infrastructure with prepare.c's+ * ExecuteQuery because teaching it to return a started+ * executor would amount to about the same amount of+ * duplication as doing it here.+ *+ */+ ExecuteStmt *stmt;+ PreparedStatement *entry;+ List *plan_list;+ ParamListInfo paramLI = NULL;++ Assert(IsA(query->utilityStmt, ExecuteStmt));+ stmt = (ExecuteStmt*)(query->utilityStmt);++ entry = FetchPreparedStatement(stmt->name, true);++ /* Shouldn't find a non-fixed-result cached plan */+ if (!entry->plansource->fixed_result)+ elog(ERROR, "EXECUTE does not support variable-result cached plans");++ /* Evaluate parameters, if any */+ if (entry->plansource->num_params > 0)+ {+ param_estate = CreateExecutorState();+ param_estate->es_param_list_info = params;+ paramLI = EvaluateParams(entry, stmt->params,+ queryString, param_estate);++ }++ cplan = GetCachedPlan(entry->plansource, paramLI, true);+ plan_list = cplan->stmt_list;++ if (list_length(plan_list) != 1)+ ereport(ERROR,+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),+ errmsg("prepared statement is not a SELECT")));++ plan = (PlannedStmt *) linitial(plan_list);+ if (!IsA(plan, PlannedStmt) ||+ plan->commandType != CMD_SELECT ||+ plan->utilityStmt != NULL)+ ereport(ERROR,+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),+ errmsg("prepared statement is not a SELECT")));++ queryString = pstrdup(entry->plansource->query_string);+ }+ else{+ elog(ERROR, "unsupported command for CREATE TABLE AS");+ abort();//silence compiler+ }+++ /*+ * 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);++ /*+ * 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+ * as were not telling it that its inserting into a table (because+ * that would be too complicated for now)+ */+ if(interpretOidsOption(into->options))+ eflags |= EXEC_FLAG_FORCE_OIDS_ON;+ else+ eflags |= EXEC_FLAG_FORCE_OIDS_OFF;++ /*+ * call ExecutorStart to prepare the plan for execution. Only+ * after this we have enough information to actually create a+ * target relation+ */+ ExecutorStart(queryDesc, eflags);++ /*+ * create the target relation+ */+ create = makeNode(CreateStmt);+ create->relation = into->rel;+ create->options = into->options;+ create->oncommit = into->onCommit;+ create->tablespacename = into->tableSpaceName;++ /*+ * 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 (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(!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;++ /*+ * We check the column type here because collOid=InvalidOid+ * denotes an invalid oid for a collatable type in+ * queryDesc->tupDesc but GetColumnDefCollation - which will+ * be called by DefineRelation - defers the default collation+ * out of that...+ * XXX: A better solution would be welcome.+ */+ CheckAttributeType(col->colname, col->typeName->typeOid, col->collOid,+ NULL, false);+ create->tableElts = lappend(create->tableElts, col);+ }++ /*+ * Actually create the target table+ */+ 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);++ }++ /*+ * Check INSERT permission on the constructed table.+ *+ * XXX: It would arguable make sense to do this check only if+ * into->skipData is true.+ */+ {+ RangeTblEntry *rte = makeNode(RangeTblEntry);+ rte->rtekind = RTE_RELATION;+ rte->relid = intoRelationId;+ rte->relkind = RELKIND_RELATION;+ rte->requiredPerms = ACL_INSERT;++ for (natt = 1; natt <= intoRelationDesc->rd_att->natts; natt++)+ rte->modifiedCols = bms_add_member(rte->modifiedCols,+ natt - FirstLowInvalidHeapAttributeNumber);++ ExecCheckRTPerms(list_make1(rte), true);+ }++ /* run cleanup too */+ ExecutorFinish(queryDesc);+ ExecutorEnd(queryDesc);++ /* close rel, but keep lock until commit */+ heap_close(intoRelationDesc, NoLock);++ FreeQueryDesc(queryDesc);++ if(cplan){+ ReleaseCachedPlan(cplan, true);++ if (param_estate)+ FreeExecutorState(param_estate);+ }++ PopActiveSnapshot();+}+
/*
* Emit the right error or warning message for a "DROP" command issued on a
* non-existent relation
@@ -10238,3 +10481,95 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
++/*+ * Support for SELECT INTO (aka 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();+ myState->hi_options = HEAP_INSERT_SKIP_FSM;+ if (!XLogIsNeeded())+ myState->hi_options |= HEAP_INSERT_SKIP_WAL;++}++/*+ * 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 on startup */++ return (DestReceiver *) self;+}diff --git a/src/backend/commands/view.c b/src/backend/commands/view.cindex 99fb7db..93e523a 100644--- a/src/backend/commands/view.c+++ b/src/backend/commands/view.c@@ -428,6 +428,22 @@ DefineView(ViewStmt *stmt, const char *queryString)
RangeVar *view;
/*
+ * We check for SELECT INTO before parse analysis because that+ * would complain about the SELECT INTO without specific enough detail.+ */+ if(IsA(stmt->query, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)stmt->query;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("views must not contain SELECT INTO")));+ }++ /*
* Run parse analysis to convert the raw parse tree to a Query. Note this
* also acquires sufficient locks on the source table(s).
*
@@ -449,10 +465,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),
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.cindex 36dcc8e..1979325 100644--- a/src/backend/executor/execMain.c+++ b/src/backend/executor/execMain.c@@ -90,12 +90,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 +168,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);
@@ -212,6 +205,13 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
estate->es_top_eflags = eflags;
estate->es_instrument = queryDesc->instrument_options;
+ if(eflags & EXEC_FLAG_FORCE_OIDS_ON)+ estate->es_force_oids = ESTATE_FORCE_OIDS_ON;+ else if(eflags & EXEC_FLAG_FORCE_OIDS_OFF)+ estate->es_force_oids = ESTATE_FORCE_OIDS_OFF;+ else+ estate->es_force_oids = ESTATE_FORCE_OIDS_DEFAULT;+
/*
* Initialize the plan state tree
*/
@@ -310,13 +310,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))
@@ -451,12 +444,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 +693,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)
{
@@ -864,18 +842,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;
@@ -926,9 +892,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 +932,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 +1184,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
@@ -1255,15 +1209,25 @@ 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().+ * SELECT INTO is even uglier, because we don't have the INTO+ * relation's descriptor available - because its only defined after+ * the query started - when this code runs; we have to look aside at+ * flags which is passed to ExecutorStart via eflags.
*/
bool
ExecContextForcesOids(PlanState *planstate, bool *hasoids)
{
ResultRelInfo *ri = planstate->state->es_result_relation_info;
+ if(planstate->state->es_force_oids == ESTATE_FORCE_OIDS_ON){+ *hasoids = true;+ return true;+ }+ else if(planstate->state->es_force_oids == ESTATE_FORCE_OIDS_OFF){+ *hasoids = false;+ return true;+ }+
if (ri != NULL)
{
Relation rel = ri->ri_RelationDesc;
@@ -1275,12 +1239,6 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids)
}
}
- if (planstate->state->es_select_into)- {- *hasoids = planstate->state->es_into_oids;- return true;- }-
return false;
}
@@ -2290,8 +2248,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 */
/*
@@ -2423,351 +2380,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);-}diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.cindex 6db42e7..40cd5ce 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.cindex 61f4622..ae8d374 100644--- a/src/backend/executor/functions.c+++ b/src/backend/executor/functions.c@@ -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;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.cindex 81f284c..ce6abcf 100644--- a/src/backend/executor/spi.c+++ b/src/backend/executor/spi.c@@ -1917,7 +1917,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;
}
/*
@@ -2042,9 +2046,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.cindex 7fec4db..e4f1815 100644--- a/src/backend/nodes/copyfuncs.c+++ b/src/backend/nodes/copyfuncs.c@@ -85,7 +85,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);
@@ -2419,7 +2418,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);
@@ -3207,6 +3205,18 @@ _copyExplainStmt(const 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(const CreateSeqStmt *from)
{
@@ -3615,7 +3625,6 @@ _copyExecuteStmt(const ExecuteStmt *from)
ExecuteStmt *newnode = makeNode(ExecuteStmt);
COPY_STRING_FIELD(name);
- COPY_NODE_FIELD(into);
COPY_NODE_FIELD(params);
return newnode;
@@ -4250,6 +4259,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;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.cindex d2a79eb..4ac895e 100644--- a/src/backend/nodes/equalfuncs.c+++ b/src/backend/nodes/equalfuncs.c@@ -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,17 @@ _equalExplainStmt(const ExplainStmt *a, const 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(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
@@ -1908,7 +1918,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 +2802,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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.cindex 25a215e..9cb8592 100644--- a/src/backend/nodes/outfuncs.c+++ b/src/backend/nodes/outfuncs.c@@ -248,7 +248,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);
@@ -2187,7 +2186,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);
@@ -2805,7 +2803,7 @@ _outNode(StringInfo str, const 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.cindex b9258ad..9b57956 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.cindex 8bbe977..6b0541b 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.cindex 40a420a..8006787 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.cindex 59a5268..0304c9d 100644--- a/src/backend/optimizer/prep/prepjointree.c+++ b/src/backend/optimizer/prep/prepjointree.c@@ -1129,8 +1129,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");
/*
@@ -1216,8 +1215,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.cindex cd3da46..7d40e3e 100644--- a/src/backend/optimizer/util/clauses.c+++ b/src/backend/optimizer/util/clauses.c@@ -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 ||
@@ -4682,8 +4681,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.cindex b187b03..182d6f7 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:
/*
@@ -257,6 +264,7 @@ analyze_requires_snapshot(Node *parseTree)
result = true;
break;
+ case T_CreateTableAsStmt:
case T_ExplainStmt:
/* yes, because we must analyze the contained statement */
result = true;
@@ -455,21 +463,29 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace;
+ /* The grammar should have produced a SELECT, but it might have INTO */+ if(IsA(stmt->selectStmt, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)stmt->selectStmt;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_SYNTAX_ERROR),+ errmsg("INSERT ... SELECT cannot specify INTO"),+ parser_errposition(pstate,+ exprLocation((Node *)first->intoClause))));+ }+
selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
free_parsestate(sub_pstate);
- /* 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 +892,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 +991,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);
@@ -1185,9 +1211,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 +1276,6 @@ static Query *
transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
{
Query *qry = makeNode(Query);
- SelectStmt *leftmostSelect;
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
@@ -1286,20 +1308,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 +1338,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))
@@ -2099,6 +2107,25 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot specify both SCROLL and NO SCROLL")));
+ /*+ * We must explicitly disallow DECLARE CURSOR ... SELECT INTO We+ * have to do this before transformStmt() as that will holler if+ * it ever finds a intoClause+ */+ if(IsA(stmt->query, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)stmt->query;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),+ errmsg("DECLARE CURSOR cannot specify INTO"),+ parser_errposition(pstate,+ exprLocation((Node *) first->intoClause))));+ }+
result = transformStmt(pstate, stmt->query);
/* Grammar should not have allowed anything but SELECT */
@@ -2107,14 +2134,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
@@ -2183,6 +2202,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.yindex d1ce2ab..2208594 100644--- a/src/backend/parser/gram.y+++ b/src/backend/parser/gram.y@@ -3052,23 +3052,28 @@ 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))));
- n->intoClause = $4;- /* cram additional flags into the IntoClause */- $4->rel->relpersistence = $2;- $4->skipData = !($7);- $$ = $6;
}
;
@@ -8275,20 +8280,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.cindex 7f4da92..52f449f 100644--- a/src/backend/parser/parse_clause.c+++ b/src/backend/parser/parse_clause.c@@ -481,6 +481,21 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
if (r->alias == NULL)
elog(ERROR, "subquery in FROM must have an alias");
++ if(IsA(r->subquery, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)r->subquery;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_SYNTAX_ERROR),+ errmsg("subquery in FROM cannot have SELECT INTO"),+ parser_errposition(pstate,+ exprLocation((Node *)first->intoClause))));+ }+
/*
* Analyze and transform the subquery.
*/
@@ -495,12 +510,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.cindex c0c6240..4ea8ee6 100644--- a/src/backend/parser/parse_cte.c+++ b/src/backend/parser/parse_cte.c@@ -241,6 +241,20 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
/* Analysis not done already */
Assert(!IsA(cte->ctequery, Query));
+ if(IsA(cte->ctequery, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)cte->ctequery;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_SYNTAX_ERROR),+ errmsg("subquery in WITH cannot have SELECT INTO"),+ parser_errposition(pstate,+ exprLocation((Node *) first->intoClause))));+ }+
query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
cte->ctequery = (Node *) query;
@@ -253,13 +267,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.
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.cindex d22d8a1..d4d9121 100644--- a/src/backend/parser/parse_expr.c+++ b/src/backend/parser/parse_expr.c@@ -1398,6 +1398,21 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
return result;
pstate->p_hasSubLinks = true;
++ if(IsA(sublink->subselect, SelectStmt))+ {+ SelectStmt* first = (SelectStmt*)sublink->subselect;+ while (first && first->op != SETOP_NONE)+ first = first->larg;++ if (first->intoClause)+ ereport(ERROR,+ (errcode(ERRCODE_SYNTAX_ERROR),+ errmsg("subquery cannot have SELECT INTO"),+ parser_errposition(pstate,+ exprLocation((Node *) first->intoClause))));+ }+
qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false);
/*
@@ -1408,12 +1423,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;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.cindex f9ec2b2..854925c 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.cindex 8c87ac5..24199e8 100644--- a/src/backend/rewrite/rewriteDefine.c+++ b/src/backend/rewrite/rewriteDefine.c@@ -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")));
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.cindex 42a0fb0..7c50284 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.cindex 5b81c0b..35ccdb3 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:
@@ -1036,6 +1035,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;
@@ -1230,8 +1233,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 +1283,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 */
@@ -1318,8 +1317,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:
@@ -1907,6 +1905,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 +2065,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 +2113,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 */
@@ -2178,10 +2179,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 */
@@ -2429,6 +2427,10 @@ GetCommandLogLevel(Node *parsetree)
}
break;
+ case T_CreateTableAsStmt:+ lev = LOGSTMT_DDL;+ break;+
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
@@ -2529,10 +2531,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 +2557,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.hindex 6a3778d..abbc152 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/prepare.h b/src/include/commands/prepare.hindex 472a357..6b545dc 100644--- a/src/include/commands/prepare.h+++ b/src/include/commands/prepare.h@@ -54,4 +54,7 @@ extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
void DropAllPreparedStatements(void);
+extern ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,+ const char *queryString, EState *estate);+
#endif /* PREPARE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.hindex 03f397d..212a51d 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 Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.hindex 7f27669..47bd1ee 100644--- a/src/include/executor/executor.h+++ b/src/include/executor/executor.h@@ -49,12 +49,17 @@
* 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.
+ *+ * FORCE_OIDS tells the executor to build a tupleDesc that includes+ * the oid column. This is used from tablecmd.c's CreateTableAs.
*/
#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_FORCE_OIDS_ON 0x0020 /* force oids in returned tuples */+#define EXEC_FLAG_FORCE_OIDS_OFF 0x0040 /* force oids in returned tuples */
/*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.hindex 5207102..d268fb9 100644--- a/src/include/nodes/execnodes.h+++ b/src/include/nodes/execnodes.h@@ -322,6 +322,12 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
} ResultRelInfo;
+typedef enum {+ ESTATE_FORCE_OIDS_DEFAULT,+ ESTATE_FORCE_OIDS_ON,+ ESTATE_FORCE_OIDS_OFF+} EStateForceOids;+
/* ----------------
* EState information
*
@@ -371,8 +377,7 @@ 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 */+ EStateForceOids es_force_oids; /* used by CreateTableAs */
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.hindex 0e7d184..f4d9ce2 100644--- a/src/include/nodes/nodes.h+++ b/src/include/nodes/nodes.h@@ -304,6 +304,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.hindex ab55639..5c2797c 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 */
@@ -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 */
@@ -2384,7 +2382,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.
* ----------------------
*/
@@ -2396,6 +2394,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
* ----------------------
*/
@@ -2509,7 +2520,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.hindex 7d90b91..b67d988 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.cindex 0b126a3..231210a 100644--- a/src/pl/plpgsql/src/pl_exec.c+++ b/src/pl/plpgsql/src/pl_exec.c@@ -3286,29 +3286,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:
@@ -5759,7 +5748,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 +5822,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/select_into.out b/src/test/regress/expected/select_into.outindex c8327f6..d124382 100644--- a/src/test/regress/expected/select_into.out+++ b/src/test/regress/expected/select_into.out@@ -44,6 +44,25 @@ CREATE TABLE selinto_schema.tmp3 (a,b,c)
AS SELECT oid,relname,relacl FROM pg_class
WHERE relname like '%c%'; -- OK
RESET SESSION AUTHORIZATION;
+--+-- disallowed uses of SELECT ... INTO. All should fail+--+DECLARE foo CURSOR FOR SELECT 1 INTO b;+ERROR: DECLARE CURSOR cannot specify INTO+LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;+ ^+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blub';+ERROR: COPY (SELECT INTO) is not supported+SELECT * FROM (SELECT 1 INTO f) bar;+ERROR: subquery in FROM cannot have SELECT INTO+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: INSERT ... SELECT cannot specify INTO+LINE 1: INSERT INTO b SELECT 1 INTO f;+ ^
DROP SCHEMA selinto_schema CASCADE;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table selinto_schema.tmp1
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.outindex e9d3908..6981692 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;
diff --git a/src/test/regress/sql/select_into.sql b/src/test/regress/sql/select_into.sqlindex 09d210b..0c07f76 100644--- a/src/test/regress/sql/select_into.sql+++ b/src/test/regress/sql/select_into.sql@@ -50,6 +50,21 @@ CREATE TABLE selinto_schema.tmp3 (a,b,c)
WHERE relname like '%c%'; -- OK
RESET SESSION AUTHORIZATION;
++--+-- 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 'blub';++SELECT * FROM (SELECT 1 INTO f) bar;++CREATE VIEW foo AS SELECT 1 INTO b;++INSERT INTO b SELECT 1 INTO f;++
DROP SCHEMA selinto_schema CASCADE;
DROP USER selinto_user;
--
1.7.6.409.ge7a85.dirty
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: andres@anarazel.de, tgl@sss.pgh.pa.us, thom@linux.com, dimitri@2ndquadrant.fr
Subject: Re: Command Triggers, patch v11
In-Reply-To: <201202280032.27171.andres@anarazel.de>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox