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
Cc: Thom Brown <thom@linux.com>
Cc: Dimitri Fontaine <dimitri@2ndquadrant.fr>
Cc: Tom Lane <tgl@sss.pgh.pa.us>
Subject: Re: Command Triggers, patch v11
Date: Sun, 4 Mar 2012 16:50:39 +0100
Message-ID: <201203041650.39826.andres@anarazel.de> (raw)
In-Reply-To: <CAA-aLv5paBN4P-cXUsu=KMQva-SCSjo9MpzM6szYFt5wqeFkeg@mail.gmail.com>
References: <m24nufq466.fsf@2ndQuadrant.fr>
	<CAA-aLv4kv3DXuwXdDyU2qcWLjvYCsVo5=qTZq+t8uk5S9sAkqQ@mail.gmail.com>
	<CAA-aLv5paBN4P-cXUsu=KMQva-SCSjo9MpzM6szYFt5wqeFkeg@mail.gmail.com>

Hi,

On Saturday, March 03, 2012 01:08:33 AM Thom Brown wrote:
> On 2 March 2012 23:33, Thom Brown <thom@linux.com> wrote:
> > On 2 March 2012 22:32, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
> >>>> test=# CREATE TABLE badname (id int, a int, b text);
> >>>> ERROR:  invalid relation name: badname
> >>>> 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.
> >> 
> >> I think Andres should add an entry for his patch on the commitfest.  Is
> >> it ok?
> > 
> > I'll try Andres' patch this weekend while I test yours.
> 
> Actually no matter which patch I apply first, they cause the other to
> fail to apply.
Ok, I rebased my patch ontop of dim's current HEAD. There was only one trivial 
conflict in tablecmds.h. I had written the patch independently of the command 
triggers stuff because I though, and still do, that would make applying it 
easier.

Attached are two versions of the patch, one based on command triggers and one 
without. Both pass regression tests for me.

Andres

Attachments: download all (2 files, .tar.gz)

ctas-01.patch (text/x-patch, 76.7K) download From a7259034845e8d3e0f2b9cd93121553580d64715 Mon Sep 17 00:00:00 2001 From: Andres Freund <andres@anarazel.de> Date: Sun, 4 Mar 2012 16:45:01 +0100 Subject: [PATCH] Transform CREATE TABLE AS/SELECT INTO into a utility statement --- 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 9aea2cd..962074c 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.9
ctas-on-command-triggers-01.patch (text/x-patch, 76.7K) download From 954990feb665b6a1ce35968d7acf77e0d1356502 Mon Sep 17 00:00:00 2001 From: Andres Freund <andres@anarazel.de> Date: Sun, 4 Mar 2012 16:45:01 +0100 Subject: [PATCH] Transform CREATE TABLE AS/SELECT INTO into a utility statement Conflicts: src/include/commands/tablecmds.h --- 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 1797e4d..cd59779 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -45,6 +45,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" @@ -69,6 +70,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" @@ -197,6 +199,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, @@ -673,6 +687,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 @@ -10306,3 +10549,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 09c6001..e12b17b 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -443,6 +443,22 @@ DefineView(ViewStmt *stmt, const char *queryString) CommandContextData cmd; /* + * 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). * @@ -464,10 +480,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 6a20a6c..790ff4c 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) { @@ -3638,7 +3648,6 @@ _copyExecuteStmt(const ExecuteStmt *from) ExecuteStmt *newnode = makeNode(ExecuteStmt); COPY_STRING_FIELD(name); - COPY_NODE_FIELD(into); COPY_NODE_FIELD(params); return newnode; @@ -4273,6 +4282,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 137075a..58194df 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) { @@ -1927,7 +1937,6 @@ static bool _equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b) { COMPARE_STRING_FIELD(name); - COMPARE_NODE_FIELD(into); COMPARE_NODE_FIELD(params); return true; @@ -2812,6 +2821,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 1d78cfd..0b596f0 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 e9872d3..553dfc9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3058,23 +3058,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; } ; @@ -8451,20 +8456,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 03c1296..eff4c75 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -329,8 +329,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 69d6479..61ce5a6 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -128,9 +128,7 @@ CommandIsReadOnly(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->intoClause != NULL) - return false; /* SELECT INTO */ - else if (stmt->rowMarks != NIL) + if (stmt->rowMarks != NIL) return false; /* SELECT FOR UPDATE/SHARE */ else if (stmt->hasModifyingCTE) return false; /* data-modifying CTE */ @@ -383,6 +381,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: @@ -1299,6 +1298,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; @@ -1507,8 +1510,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 */ @@ -1559,8 +1560,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 */ @@ -1595,8 +1594,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: @@ -2187,6 +2185,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) { @@ -2348,8 +2353,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 */ @@ -2398,8 +2401,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 */ @@ -2473,10 +2474,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 */ @@ -2724,6 +2722,10 @@ GetCommandLogLevel(Node *parsetree) } break; + case T_CreateTableAsStmt: + lev = LOGSTMT_DDL; + break; + case T_VariableSetStmt: lev = LOGSTMT_ALL; break; @@ -2836,10 +2838,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: @@ -2865,10 +2864,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 2e0b1f1..e8efce1 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -16,6 +16,7 @@ #include "access/htup.h" #include "commands/cmdtrigger.h" +#include "executor/executor.h" #include "nodes/parsenodes.h" #include "storage/lock.h" #include "utils/relcache.h" @@ -23,6 +24,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 1b7298a..9a260c7 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -305,6 +305,7 @@ typedef enum NodeTag T_DropdbStmt, T_VacuumStmt, T_ExplainStmt, + T_CreateTableAsStmt, T_CreateSeqStmt, T_AlterSeqStmt, T_VariableSetStmt, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index d4d1333..433cbc8 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 */ @@ -2430,7 +2428,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. * ---------------------- */ @@ -2442,6 +2440,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 * ---------------------- */ @@ -2555,7 +2566,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.9

view thread (115+ messages)  latest in thread

Message-ID: <201203041650.39826.andres@anarazel.de>
Permalink:  ../201203041650.39826.andres@anarazel.de/
Also on:    postgresql.org/message-id/201203041650.39826.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, thom@linux.com, dimitri@2ndquadrant.fr, tgl@sss.pgh.pa.us
  Subject: Re: Command Triggers, patch v11
  In-Reply-To: <201203041650.39826.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