pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.y index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.c index 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.h index 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.h index 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.h index 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.h index 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.h index 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.h index 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.h index 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.h index 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.c index 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.out index 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.out index 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.sql index 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

view thread (115+ messages)  latest in thread

Message-ID: <201202280032.27171.andres@anarazel.de>
Permalink:  ../201202280032.27171.andres@anarazel.de/
Also on:    postgresql.org/message-id/201202280032.27171.andres@anarazel.de

 ·  · 

reply

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