From: Tom Lane Date: Tue, 20 Mar 2012 01:37:19 +0000 (-0400) Subject: Restructure SELECT INTO's parsetree representation into CreateTableAsStmt. X-Git-Tag: REL9_2_BETA1~258 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9dbf2b7d75de5af38d087cbe2b1147dd0fd10f0a;p=postgresql Restructure SELECT INTO's parsetree representation into CreateTableAsStmt. Making this operation look like a utility statement seems generally a good idea, and particularly so in light of the desire to provide command triggers for utility statements. The original choice of representing it as SELECT with an IntoClause appendage had metastasized into rather a lot of places, unfortunately, so that this patch is a great deal more complicated than one might at first expect. In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS subcommands required restructuring some EXPLAIN-related APIs. Add-on code that calls ExplainOnePlan or ExplainOneUtility, or uses ExplainOneQuery_hook, will need adjustment. Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO, which formerly were accepted though undocumented, are no longer accepted. The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE. The CREATE RULE case doesn't seem to have much real-world use (since the rule would work only once before failing with "table already exists"), so we'll not bother with that one. Both SELECT INTO and CREATE TABLE AS still return a command tag of "SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn", but for the moment backwards compatibility wins the day. Andres Freund and Tom Lane --- diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 4af7aad00b..9573a0db45 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -13,7 +13,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ - collationcmds.o constraint.o conversioncmds.o copy.o \ + collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \ foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 517660d373..6b5bcd83c5 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1210,15 +1210,17 @@ BeginCopy(bool is_from, elog(ERROR, "unexpected rewrite result"); query = (Query *) linitial(rewritten); - Assert(query->commandType == CMD_SELECT); - Assert(query->utilityStmt == NULL); - /* Query mustn't use INTO, either */ - if (query->intoClause) + /* The grammar allows SELECT INTO, but we don't support that */ + if (query->utilityStmt != NULL && + IsA(query->utilityStmt, CreateTableAsStmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("COPY (SELECT INTO) is not supported"))); + Assert(query->commandType == CMD_SELECT); + Assert(query->utilityStmt == NULL); + /* plan the query */ plan = planner(query, 0, NULL); diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c new file mode 100644 index 0000000000..5173f5a308 --- /dev/null +++ b/src/backend/commands/createas.c @@ -0,0 +1,423 @@ +/*------------------------------------------------------------------------- + * + * createas.c + * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO + * + * We implement this by diverting the query's normal output to a + * specialized DestReceiver type. + * + * Formerly, this command was implemented as a variant of SELECT, which led + * to assorted legacy behaviors that we still try to preserve, notably that + * we must return a tuples-processed count in the completionTag. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/createas.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/xact.h" +#include "catalog/toasting.h" +#include "commands/createas.h" +#include "commands/prepare.h" +#include "commands/tablecmds.h" +#include "parser/parse_clause.h" +#include "rewrite/rewriteHandler.h" +#include "storage/smgr.h" +#include "tcop/tcopprot.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" + + +typedef struct +{ + DestReceiver pub; /* publicly-known function pointers */ + IntoClause *into; /* target relation specification */ + /* These fields are filled by intorel_startup: */ + Relation rel; /* relation to write to */ + CommandId output_cid; /* cmin to insert in output tuples */ + int hi_options; /* heap_insert performance options */ + BulkInsertState bistate; /* bulk insert state */ +} DR_intorel; + +static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); +static void intorel_receive(TupleTableSlot *slot, DestReceiver *self); +static void intorel_shutdown(DestReceiver *self); +static void intorel_destroy(DestReceiver *self); + + +/* + * ExecCreateTableAs -- execute a CREATE TABLE AS command + */ +void +ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, + ParamListInfo params, char *completionTag) +{ + Query *query = (Query *) stmt->query; + IntoClause *into = stmt->into; + DestReceiver *dest; + List *rewritten; + PlannedStmt *plan; + QueryDesc *queryDesc; + ScanDirection dir; + + /* + * Create the tuple receiver object and insert info it will need + */ + dest = CreateIntoRelDestReceiver(into); + + /* + * The contained Query could be a SELECT, or an EXECUTE utility command. + * If the latter, we just pass it off to ExecuteQuery. + */ + Assert(IsA(query, Query)); + if (query->commandType == CMD_UTILITY && + IsA(query->utilityStmt, ExecuteStmt)) + { + ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt; + + ExecuteQuery(estmt, into, queryString, params, dest, completionTag); + + return; + } + Assert(query->commandType == CMD_SELECT); + + /* + * Parse analysis was done already, but we still have to run the rule + * rewriter. We do not do AcquireRewriteLocks: we assume the query either + * came straight from the parser, or suitable locks were acquired by + * plancache.c. + * + * Because the rewriter and planner tend to scribble on the input, we make + * a preliminary copy of the source querytree. This prevents problems in + * the case that CTAS is in a portal or plpgsql function and is executed + * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + */ + rewritten = QueryRewrite((Query *) copyObject(stmt->query)); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT"); + query = (Query *) linitial(rewritten); + Assert(query->commandType == CMD_SELECT); + + /* plan the query */ + plan = pg_plan_query(query, 0, params); + + /* + * Use a snapshot with an updated command ID to ensure this query sees + * results of any previously executed queries. (This could only matter + * if the planner executed an allegedly-stable function that changed + * the database contents, but let's do it anyway to be parallel to the + * EXPLAIN code path.) + */ + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); + + /* Create a QueryDesc, redirecting output to our tuple receiver */ + queryDesc = CreateQueryDesc(plan, queryString, + GetActiveSnapshot(), InvalidSnapshot, + dest, params, 0); + + /* call ExecutorStart to prepare the plan for execution */ + ExecutorStart(queryDesc, GetIntoRelEFlags(into)); + + /* + * Normally, we run the plan to completion; but if skipData is specified, + * just do tuple receiver startup and shutdown. + */ + if (into->skipData) + dir = NoMovementScanDirection; + else + dir = ForwardScanDirection; + + /* run the plan */ + ExecutorRun(queryDesc, dir, 0L); + + /* save the rowcount if we're given a completionTag to fill */ + if (completionTag) + snprintf(completionTag, COMPLETION_TAG_BUFSIZE, + "SELECT %u", queryDesc->estate->es_processed); + + /* and clean up */ + ExecutorFinish(queryDesc); + ExecutorEnd(queryDesc); + + FreeQueryDesc(queryDesc); + + PopActiveSnapshot(); +} + +/* + * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS + * + * This is exported because EXPLAIN and PREPARE need it too. (Note: those + * callers still need to deal explicitly with the skipData flag; since they + * use different methods for suppressing execution, it doesn't seem worth + * trying to encapsulate that part.) + */ +int +GetIntoRelEFlags(IntoClause *intoClause) +{ + /* + * We need to tell the executor whether it has to produce OIDs or not, + * because it doesn't have enough information to do so itself (since we + * can't build the target relation until after ExecutorStart). + */ + if (interpretOidsOption(intoClause->options)) + return EXEC_FLAG_WITH_OIDS; + else + return EXEC_FLAG_WITHOUT_OIDS; +} + +/* + * CreateIntoRelDestReceiver -- create a suitable DestReceiver object + * + * intoClause will be NULL if called from CreateDestReceiver(), in which + * case it has to be provided later. However, it is convenient to allow + * self->into to be filled in immediately for other callers. + */ +DestReceiver * +CreateIntoRelDestReceiver(IntoClause *intoClause) +{ + DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel)); + + self->pub.receiveSlot = intorel_receive; + self->pub.rStartup = intorel_startup; + self->pub.rShutdown = intorel_shutdown; + self->pub.rDestroy = intorel_destroy; + self->pub.mydest = DestIntoRel; + self->into = intoClause; + /* other private fields will be set during intorel_startup */ + + return (DestReceiver *) self; +} + +/* + * intorel_startup --- executor startup + */ +static void +intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) +{ + DR_intorel *myState = (DR_intorel *) self; + IntoClause *into = myState->into; + CreateStmt *create; + Oid intoRelationId; + Relation intoRelationDesc; + RangeTblEntry *rte; + Datum toast_options; + ListCell *lc; + int attnum; + static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + + Assert(into != NULL); /* else somebody forgot to set it */ + + /* + * Create the target relation by faking up a CREATE TABLE parsetree and + * passing it to DefineRelation. + */ + create = makeNode(CreateStmt); + create->relation = into->rel; + create->tableElts = NIL; /* will fill below */ + create->inhRelations = NIL; + create->ofTypename = NULL; + create->constraints = NIL; + create->options = into->options; + create->oncommit = into->onCommit; + create->tablespacename = into->tableSpaceName; + create->if_not_exists = false; + + /* + * Build column definitions using "pre-cooked" type and collation info. + * If a column name list was specified in CREATE TABLE AS, override the + * column names derived from the query. (Too few column names are OK, too + * many are not.) + */ + lc = list_head(into->colNames); + for (attnum = 0; attnum < typeinfo->natts; attnum++) + { + Form_pg_attribute attribute = typeinfo->attrs[attnum]; + ColumnDef *col = makeNode(ColumnDef); + TypeName *coltype = makeNode(TypeName); + + if (lc) + { + col->colname = strVal(lfirst(lc)); + lc = lnext(lc); + } + else + col->colname = NameStr(attribute->attname); + col->typeName = coltype; + col->inhcount = 0; + col->is_local = true; + col->is_not_null = false; + col->is_from_type = false; + col->storage = 0; + col->raw_default = NULL; + col->cooked_default = NULL; + col->collClause = NULL; + col->collOid = attribute->attcollation; + col->constraints = NIL; + col->fdwoptions = NIL; + + coltype->names = NIL; + coltype->typeOid = attribute->atttypid; + coltype->setof = false; + coltype->pct_type = false; + coltype->typmods = NIL; + coltype->typemod = attribute->atttypmod; + coltype->arrayBounds = NIL; + coltype->location = -1; + + /* + * It's possible that the column is of a collatable type but the + * collation could not be resolved, so double-check. (We must + * check this here because DefineRelation would adopt the type's + * default collation rather than complaining.) + */ + if (!OidIsValid(col->collOid) && + type_is_collatable(coltype->typeOid)) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("no collation was derived for column \"%s\" with collatable type %s", + col->colname, format_type_be(coltype->typeOid)), + errhint("Use the COLLATE clause to set the collation explicitly."))); + + create->tableElts = lappend(create->tableElts, col); + } + + if (lc != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("CREATE TABLE AS specifies too many column names"))); + + /* + * Actually create the target table + */ + intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid); + + /* + * If necessary, create a TOAST table for the target table. Note that + * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that + * the TOAST table will be visible for insertion. + */ + CommandCounterIncrement(); + + /* parse and validate reloptions for the toast table */ + toast_options = transformRelOptions((Datum) 0, + create->options, + "toast", + validnsps, + true, false); + + (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); + + AlterTableCreateToastTable(intoRelationId, toast_options); + + /* + * Finally we can open the target table + */ + intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); + + /* + * Check INSERT permission on the constructed table. + * + * XXX: It would arguably make sense to skip this check if into->skipData + * is true. + */ + rte = makeNode(RangeTblEntry); + rte->rtekind = RTE_RELATION; + rte->relid = intoRelationId; + rte->relkind = RELKIND_RELATION; + rte->requiredPerms = ACL_INSERT; + + for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++) + rte->modifiedCols = bms_add_member(rte->modifiedCols, + attnum - FirstLowInvalidHeapAttributeNumber); + + ExecCheckRTPerms(list_make1(rte), true); + + /* + * Fill private fields of myState for use by later routines + */ + myState->rel = intoRelationDesc; + myState->output_cid = GetCurrentCommandId(true); + + /* + * We can skip WAL-logging the insertions, unless PITR or streaming + * replication is in use. We can skip the FSM in any case. + */ + myState->hi_options = HEAP_INSERT_SKIP_FSM | + (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL); + myState->bistate = GetBulkInsertState(); + + /* Not using WAL requires smgr_targblock be initially invalid */ + Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber); +} + +/* + * intorel_receive --- receive one tuple + */ +static void +intorel_receive(TupleTableSlot *slot, DestReceiver *self) +{ + DR_intorel *myState = (DR_intorel *) self; + HeapTuple tuple; + + /* + * get the heap tuple out of the tuple table slot, making sure we have a + * writable copy + */ + tuple = ExecMaterializeSlot(slot); + + /* + * force assignment of new OID (see comments in ExecInsert) + */ + if (myState->rel->rd_rel->relhasoids) + HeapTupleSetOid(tuple, InvalidOid); + + heap_insert(myState->rel, + tuple, + myState->output_cid, + myState->hi_options, + myState->bistate); + + /* We know this is a newly created relation, so there are no indexes */ +} + +/* + * intorel_shutdown --- executor end + */ +static void +intorel_shutdown(DestReceiver *self) +{ + DR_intorel *myState = (DR_intorel *) self; + + FreeBulkInsertState(myState->bistate); + + /* If we skipped using WAL, must heap_sync before commit */ + if (myState->hi_options & HEAP_INSERT_SKIP_WAL) + heap_sync(myState->rel); + + /* close rel, but keep lock until commit */ + heap_close(myState->rel, NoLock); + myState->rel = NULL; +} + +/* + * intorel_destroy --- release DestReceiver object + */ +static void +intorel_destroy(DestReceiver *self) +{ + pfree(self); +} diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 93b1f34ca0..a14cae1442 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -15,6 +15,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" +#include "commands/createas.h" #include "commands/defrem.h" #include "commands/prepare.h" #include "executor/hashjoin.h" @@ -45,7 +46,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; #define X_CLOSE_IMMEDIATE 2 #define X_NOWHITESPACE 4 -static void ExplainOneQuery(Query *query, ExplainState *es, +static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es); @@ -212,7 +213,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, /* Explain every plan */ foreach(l, rewritten) { - ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); + ExplainOneQuery((Query *) lfirst(l), NULL, &es, + queryString, params); /* Separate plans with an appropriate separator */ if (lnext(l) != NULL) @@ -288,21 +290,23 @@ ExplainResultDesc(ExplainStmt *stmt) /* * ExplainOneQuery - * print out the execution plan for one Query + * + * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. */ static void -ExplainOneQuery(Query *query, ExplainState *es, +ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params) { /* planner will not cope with utility statements */ if (query->commandType == CMD_UTILITY) { - ExplainOneUtility(query->utilityStmt, es, queryString, params); + ExplainOneUtility(query->utilityStmt, into, es, queryString, params); return; } /* if an advisor plugin is present, let it manage things */ if (ExplainOneQuery_hook) - (*ExplainOneQuery_hook) (query, es, queryString, params); + (*ExplainOneQuery_hook) (query, into, es, queryString, params); else { PlannedStmt *plan; @@ -311,7 +315,7 @@ ExplainOneQuery(Query *query, ExplainState *es, plan = pg_plan_query(query, 0, params); /* run it (if needed) and produce output */ - ExplainOnePlan(plan, es, queryString, params); + ExplainOnePlan(plan, into, es, queryString, params); } } @@ -321,18 +325,36 @@ ExplainOneQuery(Query *query, ExplainState *es, * (In general, utility statements don't have plans, but there are some * we treat as special cases) * + * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt. + * * This is exported because it's called back from prepare.c in the - * EXPLAIN EXECUTE case + * EXPLAIN EXECUTE case. */ void -ExplainOneUtility(Node *utilityStmt, ExplainState *es, +ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params) { if (utilityStmt == NULL) return; - if (IsA(utilityStmt, ExecuteStmt)) - ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, + if (IsA(utilityStmt, CreateTableAsStmt)) + { + /* + * We have to rewrite the contained SELECT and then pass it back + * to ExplainOneQuery. It's probably not really necessary to copy + * the contained parsetree another time, but let's be safe. + */ + CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt; + List *rewritten; + + Assert(IsA(ctas->query, Query)); + rewritten = QueryRewrite((Query *) copyObject(ctas->query)); + Assert(list_length(rewritten) == 1); + ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es, + queryString, params); + } + else if (IsA(utilityStmt, ExecuteStmt)) + ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es, queryString, params); else if (IsA(utilityStmt, NotifyStmt)) { @@ -356,6 +378,9 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es, * given a planned query, execute it if needed, and then print * EXPLAIN output * + * "into" is NULL unless we are explaining the contents of a CreateTableAsStmt, + * in which case executing the query should result in creating that table. + * * Since we ignore any DeclareCursorStmt that might be attached to the query, * if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the * query. This is different from pre-8.3 behavior but seems more useful than @@ -366,9 +391,10 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es, * to call it. */ void -ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, +ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params) { + DestReceiver *dest; QueryDesc *queryDesc; instr_time starttime; double totaltime = 0; @@ -392,16 +418,27 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, PushCopiedSnapshot(GetActiveSnapshot()); UpdateActiveSnapshotCommandId(); - /* Create a QueryDesc requesting no output */ + /* + * Normally we discard the query's output, but if explaining CREATE TABLE + * AS, we'd better use the appropriate tuple receiver. + */ + if (into) + dest = CreateIntoRelDestReceiver(into); + else + dest = None_Receiver; + + /* Create a QueryDesc for the query */ queryDesc = CreateQueryDesc(plannedstmt, queryString, GetActiveSnapshot(), InvalidSnapshot, - None_Receiver, params, instrument_option); + dest, params, instrument_option); /* Select execution options */ if (es->analyze) eflags = 0; /* default run-to-completion flags */ else eflags = EXEC_FLAG_EXPLAIN_ONLY; + if (into) + eflags |= GetIntoRelEFlags(into); /* call ExecutorStart to prepare the plan for execution */ ExecutorStart(queryDesc, eflags); @@ -409,8 +446,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, /* Execute the plan for statistics if asked for */ if (es->analyze) { + ScanDirection dir; + + /* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */ + if (into && into->skipData) + dir = NoMovementScanDirection; + else + dir = ForwardScanDirection; + /* run the plan */ - ExecutorRun(queryDesc, ForwardScanDirection, 0L); + ExecutorRun(queryDesc, dir, 0L); /* run cleanup too */ ExecutorFinish(queryDesc); diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 1c7a1c3a33..e402042332 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -121,7 +121,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params, /* * Start execution, inserting parameters if any. */ - PortalStart(portal, params, true); + PortalStart(portal, params, 0, true); Assert(portal->strategy == PORTAL_ONE_SELECT); diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 4883abe470..edd646e7c3 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -18,6 +18,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" +#include "commands/createas.h" #include "commands/prepare.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -170,7 +171,12 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) } /* - * Implements the 'EXECUTE' utility statement. + * ExecuteQuery --- implement the 'EXECUTE' utility statement. + * + * This code also supports CREATE TABLE ... AS EXECUTE. That case is + * indicated by passing a non-null intoClause. The DestReceiver is already + * set up correctly for CREATE TABLE AS, but we still have to make a few + * other adjustments here. * * Note: this is one of very few places in the code that needs to deal with * two query strings at once. The passed-in queryString is that of the @@ -179,8 +185,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) * source is that of the original PREPARE. */ void -ExecuteQuery(ExecuteStmt *stmt, const char *queryString, - ParamListInfo params, +ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, + const char *queryString, ParamListInfo params, DestReceiver *dest, char *completionTag) { PreparedStatement *entry; @@ -190,6 +196,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, EState *estate = NULL; Portal portal; char *query_string; + int eflags; + long count; /* Look it up in the hash table */ entry = FetchPreparedStatement(stmt->name, true); @@ -222,25 +230,27 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, query_string = MemoryContextStrdup(PortalGetHeapMemory(portal), entry->plansource->query_string); + /* Replan if needed, and increment plan refcount for portal */ + cplan = GetCachedPlan(entry->plansource, paramLI, false); + plan_list = cplan->stmt_list; + /* - * For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query - * so that we can modify its destination (yech, but this has always been - * ugly). For regular EXECUTE we can just use the cached query, since the - * executor is read-only. + * For CREATE TABLE ... AS EXECUTE, we must verify that the prepared + * statement is one that produces tuples. Currently we insist that it be + * a plain old SELECT. In future we might consider supporting other + * things such as INSERT ... RETURNING, but there are a couple of issues + * to be settled first, notably how WITH NO DATA should be handled in such + * a case (do we really want to suppress execution?) and how to pass down + * the OID-determining eflags (PortalStart won't handle them in such a + * case, and for that matter it's not clear the executor will either). + * + * For CREATE TABLE ... AS EXECUTE, we also have to ensure that the + * proper eflags and fetch count are passed to PortalStart/PortalRun. */ - if (stmt->into) + if (intoClause) { - MemoryContext oldContext; PlannedStmt *pstmt; - /* Replan if needed, and increment plan refcount transiently */ - cplan = GetCachedPlan(entry->plansource, paramLI, true); - - /* Copy plan into portal's context, and modify */ - oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - - plan_list = copyObject(cplan->stmt_list); - if (list_length(plan_list) != 1) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -252,20 +262,21 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); - pstmt->intoClause = copyObject(stmt->into); - MemoryContextSwitchTo(oldContext); + /* Set appropriate eflags */ + eflags = GetIntoRelEFlags(intoClause); - /* We no longer need the cached plan refcount ... */ - ReleaseCachedPlan(cplan, true); - /* ... and we don't want the portal to depend on it, either */ - cplan = NULL; + /* And tell PortalRun whether to run to completion or not */ + if (intoClause->skipData) + count = 0; + else + count = FETCH_ALL; } else { - /* Replan if needed, and increment plan refcount for portal */ - cplan = GetCachedPlan(entry->plansource, paramLI, false); - plan_list = cplan->stmt_list; + /* Plain old EXECUTE */ + eflags = 0; + count = FETCH_ALL; } PortalDefineQuery(portal, @@ -276,11 +287,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, cplan); /* - * Run the portal to completion. + * Run the portal as appropriate. */ - PortalStart(portal, paramLI, true); + PortalStart(portal, paramLI, eflags, true); - (void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag); + (void) PortalRun(portal, count, false, dest, dest, completionTag); PortalDrop(portal, false); @@ -615,11 +626,14 @@ DropAllPreparedStatements(void) /* * Implements the 'EXPLAIN EXECUTE' utility statement. * + * "into" is NULL unless we are doing EXPLAIN CREATE TABLE AS EXECUTE, + * in which case executing the query should result in creating that table. + * * Note: the passed-in queryString is that of the EXPLAIN EXECUTE, * not the original PREPARE; we get the latter string from the plancache. */ void -ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, +ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params) { PreparedStatement *entry; @@ -665,27 +679,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, PlannedStmt *pstmt = (PlannedStmt *) lfirst(p); if (IsA(pstmt, PlannedStmt)) - { - if (execstmt->into) - { - if (pstmt->commandType != CMD_SELECT || - pstmt->utilityStmt != NULL) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("prepared statement is not a SELECT"))); - - /* Copy the stmt so we can modify it */ - pstmt = copyObject(pstmt); - - pstmt->intoClause = execstmt->into; - } - - ExplainOnePlan(pstmt, es, query_string, paramLI); - } + ExplainOnePlan(pstmt, into, es, query_string, paramLI); else - { - ExplainOneUtility((Node *) pstmt, es, query_string, paramLI); - } + ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI); /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 99fb7dbb8f..c887961bc9 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -439,9 +439,17 @@ DefineView(ViewStmt *stmt, const char *queryString) /* * The grammar should ensure that the result is a single SELECT Query. + * However, it doesn't forbid SELECT INTO, so we have to check for that. */ - if (!IsA(viewParse, Query) || - viewParse->commandType != CMD_SELECT) + if (!IsA(viewParse, Query)) + elog(ERROR, "unexpected parse analysis result"); + if (viewParse->utilityStmt != NULL && + IsA(viewParse->utilityStmt, CreateTableAsStmt)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("views must not contain SELECT INTO"))); + if (viewParse->commandType != CMD_SELECT || + viewParse->utilityStmt != NULL) elog(ERROR, "unexpected parse analysis result"); /* @@ -449,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString) * DefineQueryRewrite(), but that function will complain about a bogus ON * SELECT rule, and we'd rather the message complain about a view. */ - if (viewParse->intoClause != NULL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("views must not contain SELECT INTO"))); if (viewParse->hasModifyingCTE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 36dcc8e4b5..fbb36fa6dc 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -37,27 +37,20 @@ */ #include "postgres.h" -#include "access/reloptions.h" #include "access/sysattr.h" #include "access/transam.h" #include "access/xact.h" -#include "catalog/heap.h" #include "catalog/namespace.h" -#include "catalog/toasting.h" -#include "commands/tablespace.h" #include "commands/trigger.h" #include "executor/execdebug.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "optimizer/clauses.h" -#include "parser/parse_clause.h" #include "parser/parsetree.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" -#include "storage/smgr.h" #include "tcop/utility.h" #include "utils/acl.h" -#include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/snapmgr.h" @@ -90,12 +83,6 @@ static char *ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); -static void OpenIntoRel(QueryDesc *queryDesc); -static void CloseIntoRel(QueryDesc *queryDesc); -static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); -static void intorel_receive(TupleTableSlot *slot, DestReceiver *self); -static void intorel_shutdown(DestReceiver *self); -static void intorel_destroy(DestReceiver *self); /* end of local decls */ @@ -174,11 +161,9 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) case CMD_SELECT: /* - * SELECT INTO, SELECT FOR UPDATE/SHARE and modifying CTEs need to - * mark tuples + * SELECT FOR UPDATE/SHARE and modifying CTEs need to mark tuples */ - if (queryDesc->plannedstmt->intoClause != NULL || - queryDesc->plannedstmt->rowMarks != NIL || + if (queryDesc->plannedstmt->rowMarks != NIL || queryDesc->plannedstmt->hasModifyingCTE) estate->es_output_cid = GetCurrentCommandId(true); @@ -309,13 +294,6 @@ standard_ExecutorRun(QueryDesc *queryDesc, if (sendTuples) (*dest->rStartup) (dest, operation, queryDesc->tupDesc); - /* - * if it's CREATE TABLE AS ... WITH NO DATA, skip plan execution - */ - if (estate->es_select_into && - queryDesc->plannedstmt->intoClause->skipData) - direction = NoMovementScanDirection; - /* * run plan */ @@ -451,12 +429,6 @@ standard_ExecutorEnd(QueryDesc *queryDesc) ExecEndPlan(queryDesc->planstate, estate); - /* - * Close the SELECT INTO relation if any - */ - if (estate->es_select_into) - CloseIntoRel(queryDesc); - /* do away with our snapshots */ UnregisterSnapshot(estate->es_snapshot); UnregisterSnapshot(estate->es_crosscheck_snapshot); @@ -706,15 +678,6 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) { ListCell *l; - /* - * CREATE TABLE AS or SELECT INTO? - * - * XXX should we allow this if the destination is temp? Considering that - * it would still require catalog changes, probably not. - */ - if (plannedstmt->intoClause != NULL) - PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt)); - /* Fail if write permissions are requested on any non-temp table */ foreach(l, plannedstmt->rtable) { @@ -863,18 +826,6 @@ InitPlan(QueryDesc *queryDesc, int eflags) estate->es_rowMarks = lappend(estate->es_rowMarks, erm); } - /* - * Detect whether we're doing SELECT INTO. If so, set the es_into_oids - * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. (Other SELECT INTO stuff comes later.) - */ - estate->es_select_into = false; - if (operation == CMD_SELECT && plannedstmt->intoClause != NULL) - { - estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options); - } - /* * Initialize the executor's tuple table to empty. */ @@ -926,9 +877,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) planstate = ExecInitNode(plan, estate, eflags); /* - * Get the tuple descriptor describing the type of tuples to return. (this - * is especially important if we are creating a relation with "SELECT - * INTO") + * Get the tuple descriptor describing the type of tuples to return. */ tupType = ExecGetResultType(planstate); @@ -968,16 +917,6 @@ InitPlan(QueryDesc *queryDesc, int eflags) queryDesc->tupDesc = tupType; queryDesc->planstate = planstate; - - /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. - */ - if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) - OpenIntoRel(queryDesc); } /* @@ -1230,7 +1169,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) /* * ExecContextForcesOids * - * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO, + * This is pretty grotty: when doing INSERT, UPDATE, or CREATE TABLE AS, * we need to ensure that result tuples have space for an OID iff they are * going to be stored into a relation that has OIDs. In other contexts * we are free to choose whether to leave space for OIDs in result tuples @@ -1255,9 +1194,9 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) * the ModifyTable node, so ModifyTable has to set es_result_relation_info * while initializing each subplan. * - * SELECT INTO is even uglier, because we don't have the INTO relation's - * descriptor available when this code runs; we have to look aside at a - * flag set by InitPlan(). + * CREATE TABLE AS is even uglier, because we don't have the target relation's + * descriptor available when this code runs; we have to look aside at the + * flags passed to ExecutorStart(). */ bool ExecContextForcesOids(PlanState *planstate, bool *hasoids) @@ -1275,9 +1214,14 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids) } } - if (planstate->state->es_select_into) + if (planstate->state->es_top_eflags & EXEC_FLAG_WITH_OIDS) { - *hasoids = planstate->state->es_into_oids; + *hasoids = true; + return true; + } + if (planstate->state->es_top_eflags & EXEC_FLAG_WITHOUT_OIDS) + { + *hasoids = false; return true; } @@ -2290,8 +2234,6 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree) estate->es_rowMarks = parentestate->es_rowMarks; estate->es_top_eflags = parentestate->es_top_eflags; estate->es_instrument = parentestate->es_instrument; - estate->es_select_into = parentestate->es_select_into; - estate->es_into_oids = parentestate->es_into_oids; /* es_auxmodifytables must NOT be copied */ /* @@ -2423,351 +2365,3 @@ EvalPlanQualEnd(EPQState *epqstate) epqstate->planstate = NULL; epqstate->origslot = NULL; } - - -/* - * Support for SELECT INTO (a/k/a CREATE TABLE AS) - * - * We implement SELECT INTO by diverting SELECT's normal output with - * a specialized DestReceiver type. - */ - -typedef struct -{ - DestReceiver pub; /* publicly-known function pointers */ - EState *estate; /* EState we are working with */ - DestReceiver *origdest; /* QueryDesc's original receiver */ - Relation rel; /* Relation to write to */ - int hi_options; /* heap_insert performance options */ - BulkInsertState bistate; /* bulk insert state */ -} DR_intorel; - -/* - * OpenIntoRel --- actually create the SELECT INTO target relation - * - * This also replaces QueryDesc->dest with the special DestReceiver for - * SELECT INTO. We assume that the correct result tuple type has already - * been placed in queryDesc->tupDesc. - */ -static void -OpenIntoRel(QueryDesc *queryDesc) -{ - IntoClause *into = queryDesc->plannedstmt->intoClause; - EState *estate = queryDesc->estate; - TupleDesc intoTupDesc = queryDesc->tupDesc; - Relation intoRelationDesc; - char *intoName; - Oid namespaceId; - Oid tablespaceId; - Datum reloptions; - Oid intoRelationId; - DR_intorel *myState; - RangeTblEntry *rte; - AttrNumber attnum; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; - - Assert(into); - - /* - * XXX This code needs to be kept in sync with DefineRelation(). Maybe we - * should try to use that function instead. - */ - - /* - * Check consistency of arguments - */ - if (into->onCommit != ONCOMMIT_NOOP - && into->rel->relpersistence != RELPERSISTENCE_TEMP) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("ON COMMIT can only be used on temporary tables"))); - - { - AclResult aclresult; - int i; - - for (i = 0; i < intoTupDesc->natts; i++) - { - Oid atttypid = intoTupDesc->attrs[i]->atttypid; - - aclresult = pg_type_aclcheck(atttypid, GetUserId(), ACL_USAGE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_TYPE, - format_type_be(atttypid)); - } - } - - /* - * If a column name list was specified in CREATE TABLE AS, override the - * column names derived from the query. (Too few column names are OK, too - * many are not.) It would probably be all right to scribble directly on - * the query's result tupdesc, but let's be safe and make a copy. - */ - if (into->colNames) - { - ListCell *lc; - - intoTupDesc = CreateTupleDescCopy(intoTupDesc); - attnum = 1; - foreach(lc, into->colNames) - { - char *colname = strVal(lfirst(lc)); - - if (attnum > intoTupDesc->natts) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE TABLE AS specifies too many column names"))); - namestrcpy(&(intoTupDesc->attrs[attnum - 1]->attname), colname); - attnum++; - } - } - - /* - * Find namespace to create in, check its permissions, lock it against - * concurrent drop, and mark into->rel as RELPERSISTENCE_TEMP if the - * selected namespace is temporary. - */ - intoName = into->rel->relname; - namespaceId = RangeVarGetAndCheckCreationNamespace(into->rel, NoLock, - NULL); - - /* - * Security check: disallow creating temp tables from security-restricted - * code. This is needed because calling code might not expect untrusted - * tables to appear in pg_temp at the front of its search path. - */ - if (into->rel->relpersistence == RELPERSISTENCE_TEMP - && InSecurityRestrictedOperation()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("cannot create temporary table within security-restricted operation"))); - - /* - * Select tablespace to use. If not specified, use default tablespace - * (which may in turn default to database's default). - */ - if (into->tableSpaceName) - { - tablespaceId = get_tablespace_oid(into->tableSpaceName, false); - } - else - { - tablespaceId = GetDefaultTablespace(into->rel->relpersistence); - /* note InvalidOid is OK in this case */ - } - - /* Check permissions except when using the database's default space */ - if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace) - { - AclResult aclresult; - - aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), - ACL_CREATE); - - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_TABLESPACE, - get_tablespace_name(tablespaceId)); - } - - /* Parse and validate any reloptions */ - reloptions = transformRelOptions((Datum) 0, - into->options, - NULL, - validnsps, - true, - false); - (void) heap_reloptions(RELKIND_RELATION, reloptions, true); - - /* Now we can actually create the new relation */ - intoRelationId = heap_create_with_catalog(intoName, - namespaceId, - tablespaceId, - InvalidOid, - InvalidOid, - InvalidOid, - GetUserId(), - intoTupDesc, - NIL, - RELKIND_RELATION, - into->rel->relpersistence, - false, - false, - true, - 0, - into->onCommit, - reloptions, - true, - allowSystemTableMods); - Assert(intoRelationId != InvalidOid); - - /* - * Advance command counter so that the newly-created relation's catalog - * tuples will be visible to heap_open. - */ - CommandCounterIncrement(); - - /* - * If necessary, create a TOAST table for the INTO relation. Note that - * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that - * the TOAST table will be visible for insertion. - */ - reloptions = transformRelOptions((Datum) 0, - into->options, - "toast", - validnsps, - true, - false); - - (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true); - - AlterTableCreateToastTable(intoRelationId, reloptions); - - /* - * And open the constructed table for writing. - */ - intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); - - /* - * Check INSERT permission on the constructed table. - */ - rte = makeNode(RangeTblEntry); - rte->rtekind = RTE_RELATION; - rte->relid = intoRelationId; - rte->relkind = RELKIND_RELATION; - rte->requiredPerms = ACL_INSERT; - - for (attnum = 1; attnum <= intoTupDesc->natts; attnum++) - rte->modifiedCols = bms_add_member(rte->modifiedCols, - attnum - FirstLowInvalidHeapAttributeNumber); - - ExecCheckRTPerms(list_make1(rte), true); - - /* - * Now replace the query's DestReceiver with one for SELECT INTO - */ - myState = (DR_intorel *) CreateDestReceiver(DestIntoRel); - Assert(myState->pub.mydest == DestIntoRel); - myState->estate = estate; - myState->origdest = queryDesc->dest; - myState->rel = intoRelationDesc; - - queryDesc->dest = (DestReceiver *) myState; - - /* - * We can skip WAL-logging the insertions, unless PITR or streaming - * replication is in use. We can skip the FSM in any case. - */ - myState->hi_options = HEAP_INSERT_SKIP_FSM | - (XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL); - myState->bistate = GetBulkInsertState(); - - /* Not using WAL requires smgr_targblock be initially invalid */ - Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber); -} - -/* - * CloseIntoRel --- clean up SELECT INTO at ExecutorEnd time - */ -static void -CloseIntoRel(QueryDesc *queryDesc) -{ - DR_intorel *myState = (DR_intorel *) queryDesc->dest; - - /* - * OpenIntoRel might never have gotten called, and we also want to guard - * against double destruction. - */ - if (myState && myState->pub.mydest == DestIntoRel) - { - FreeBulkInsertState(myState->bistate); - - /* If we skipped using WAL, must heap_sync before commit */ - if (myState->hi_options & HEAP_INSERT_SKIP_WAL) - heap_sync(myState->rel); - - /* close rel, but keep lock until commit */ - heap_close(myState->rel, NoLock); - - /* restore the receiver belonging to executor's caller */ - queryDesc->dest = myState->origdest; - - /* might as well invoke my destructor */ - intorel_destroy((DestReceiver *) myState); - } -} - -/* - * CreateIntoRelDestReceiver -- create a suitable DestReceiver object - */ -DestReceiver * -CreateIntoRelDestReceiver(void) -{ - DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel)); - - self->pub.receiveSlot = intorel_receive; - self->pub.rStartup = intorel_startup; - self->pub.rShutdown = intorel_shutdown; - self->pub.rDestroy = intorel_destroy; - self->pub.mydest = DestIntoRel; - - /* private fields will be set by OpenIntoRel */ - - return (DestReceiver *) self; -} - -/* - * intorel_startup --- executor startup - */ -static void -intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) -{ - /* no-op */ -} - -/* - * intorel_receive --- receive one tuple - */ -static void -intorel_receive(TupleTableSlot *slot, DestReceiver *self) -{ - DR_intorel *myState = (DR_intorel *) self; - HeapTuple tuple; - - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecMaterializeSlot(slot); - - /* - * force assignment of new OID (see comments in ExecInsert) - */ - if (myState->rel->rd_rel->relhasoids) - HeapTupleSetOid(tuple, InvalidOid); - - heap_insert(myState->rel, - tuple, - myState->estate->es_output_cid, - myState->hi_options, - myState->bistate); - - /* We know this is a newly created relation, so there are no indexes */ -} - -/* - * intorel_shutdown --- executor end - */ -static void -intorel_shutdown(DestReceiver *self) -{ - /* no-op */ -} - -/* - * intorel_destroy --- release DestReceiver object - */ -static void -intorel_destroy(DestReceiver *self) -{ - pfree(self); -} diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 6db42e7b97..40cd5ce5d1 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 61f462254f..ae8d374db2 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 81f284ca04..5e4ae426b1 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -1284,11 +1284,11 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, * Start portal execution. */ if (read_only) - PortalStart(portal, paramLI, true); + PortalStart(portal, paramLI, 0, true); else { CommandCounterIncrement(); - PortalStart(portal, paramLI, false); + PortalStart(portal, paramLI, 0, false); } Assert(portal->strategy != PORTAL_MULTI_QUERY); @@ -1907,17 +1907,39 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, } else { + char completionTag[COMPLETION_TAG_BUFSIZE]; + ProcessUtility(stmt, plansource->query_string, paramLI, false, /* not top level */ dest, - NULL); + completionTag); + /* Update "processed" if stmt returned tuples */ if (_SPI_current->tuptable) _SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free; - res = SPI_OK_UTILITY; + + /* + * CREATE TABLE AS is a messy special case for historical + * reasons. We must set _SPI_current->processed even though + * the tuples weren't returned to the caller, and we must + * return a special result code if the statement was spelled + * SELECT INTO. + */ + if (IsA(stmt, CreateTableAsStmt)) + { + Assert(strncmp(completionTag, "SELECT ", 7) == 0); + _SPI_current->processed = strtoul(completionTag + 7, + NULL, 10); + if (((CreateTableAsStmt *) stmt)->is_select_into) + res = SPI_OK_SELINTO; + else + res = SPI_OK_UTILITY; + } + else + res = SPI_OK_UTILITY; } /* @@ -2042,9 +2064,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount) { case CMD_SELECT: Assert(queryDesc->plannedstmt->utilityStmt == NULL); - if (queryDesc->plannedstmt->intoClause) /* select into table? */ - res = SPI_OK_SELINTO; - else if (queryDesc->dest->mydest != DestSPI) + if (queryDesc->dest->mydest != DestSPI) { /* Don't return SPI_OK_SELECT if we're discarding result */ res = SPI_OK_UTILITY; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 5cde22543f..cf23b08872 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -86,7 +86,6 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); COPY_NODE_FIELD(utilityStmt); - COPY_NODE_FIELD(intoClause); COPY_NODE_FIELD(subplans); COPY_BITMAPSET_FIELD(rewindPlanIDs); COPY_NODE_FIELD(rowMarks); @@ -2406,7 +2405,6 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(canSetTag); COPY_NODE_FIELD(utilityStmt); COPY_SCALAR_FIELD(resultRelation); - COPY_NODE_FIELD(intoClause); COPY_SCALAR_FIELD(hasAggs); COPY_SCALAR_FIELD(hasWindowFuncs); COPY_SCALAR_FIELD(hasSubLinks); @@ -3194,6 +3192,18 @@ _copyExplainStmt(const ExplainStmt *from) return newnode; } +static CreateTableAsStmt * +_copyCreateTableAsStmt(const CreateTableAsStmt *from) +{ + CreateTableAsStmt *newnode = makeNode(CreateTableAsStmt); + + COPY_NODE_FIELD(query); + COPY_NODE_FIELD(into); + COPY_SCALAR_FIELD(is_select_into); + + return newnode; +} + static CreateSeqStmt * _copyCreateSeqStmt(const CreateSeqStmt *from) { @@ -3602,7 +3612,6 @@ _copyExecuteStmt(const ExecuteStmt *from) ExecuteStmt *newnode = makeNode(ExecuteStmt); COPY_STRING_FIELD(name); - COPY_NODE_FIELD(into); COPY_NODE_FIELD(params); return newnode; @@ -4234,6 +4243,9 @@ copyObject(const void *from) case T_ExplainStmt: retval = _copyExplainStmt(from); break; + case T_CreateTableAsStmt: + retval = _copyCreateTableAsStmt(from); + break; case T_CreateSeqStmt: retval = _copyCreateSeqStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index d2a79eb851..5e691f96f7 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,16 @@ _equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b) return true; } +static bool +_equalCreateTableAsStmt(const CreateTableAsStmt *a, const CreateTableAsStmt *b) +{ + COMPARE_NODE_FIELD(query); + COMPARE_NODE_FIELD(into); + COMPARE_SCALAR_FIELD(is_select_into); + + return true; +} + static bool _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b) { @@ -1908,7 +1917,6 @@ static bool _equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b) { COMPARE_STRING_FIELD(name); - COMPARE_NODE_FIELD(into); COMPARE_NODE_FIELD(params); return true; @@ -2793,6 +2801,9 @@ equal(const void *a, const void *b) case T_ExplainStmt: retval = _equalExplainStmt(a, b); break; + case T_CreateTableAsStmt: + retval = _equalCreateTableAsStmt(a, b); + break; case T_CreateSeqStmt: retval = _equalCreateSeqStmt(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 51181a9a74..e925434eb3 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -250,7 +250,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); WRITE_NODE_FIELD(utilityStmt); - WRITE_NODE_FIELD(intoClause); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_NODE_FIELD(rowMarks); @@ -2181,7 +2180,6 @@ _outQuery(StringInfo str, const Query *node) appendStringInfo(str, " :utilityStmt <>"); WRITE_INT_FIELD(resultRelation); - WRITE_NODE_FIELD(intoClause); WRITE_BOOL_FIELD(hasAggs); WRITE_BOOL_FIELD(hasWindowFuncs); WRITE_BOOL_FIELD(hasSubLinks); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index b9258ad70e..9b579560c5 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 8bbe97713b..6b0541b9b5 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/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 69396694aa..9e347ce736 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -22,6 +22,7 @@ #include "optimizer/pathnode.h" #include "optimizer/planmain.h" #include "optimizer/tlist.h" +#include "tcop/utility.h" #include "utils/lsyscache.h" #include "utils/syscache.h" @@ -1887,16 +1888,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) Query *query = (Query *) node; ListCell *lc; - if (query->commandType == CMD_UTILITY) + while (query->commandType == CMD_UTILITY) { - /* Ignore utility statements, except EXPLAIN */ - if (IsA(query->utilityStmt, ExplainStmt)) - { - query = (Query *) ((ExplainStmt *) query->utilityStmt)->query; - Assert(IsA(query, Query)); - Assert(query->commandType != CMD_UTILITY); - } - else + /* + * Ignore utility statements, except those (such as EXPLAIN) that + * contain a parsed-but-not-planned query. + */ + query = UtilityContainsQuery(query->utilityStmt); + if (query == NULL) return false; } diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index b64db1e1c0..f30f02f266 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 2fd795b459..c1b2f1db11 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1136,8 +1136,7 @@ is_simple_subquery(Query *subquery) */ if (!IsA(subquery, Query) || subquery->commandType != CMD_SELECT || - subquery->utilityStmt != NULL || - subquery->intoClause != NULL) + subquery->utilityStmt != NULL) elog(ERROR, "subquery is bogus"); /* @@ -1223,8 +1222,7 @@ is_simple_union_all(Query *subquery) /* Let's just make sure it's a valid subselect ... */ if (!IsA(subquery, Query) || subquery->commandType != CMD_SELECT || - subquery->utilityStmt != NULL || - subquery->intoClause != NULL) + subquery->utilityStmt != NULL) elog(ERROR, "subquery is bogus"); /* Is it a set-operation query at all? */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index cd3da46bc5..b14ae2e367 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4158,7 +4158,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, pstate->p_sourcetext = src; sql_fn_parser_setup(pstate, pinfo); - querytree = transformStmt(pstate, linitial(raw_parsetree_list)); + querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list)); free_parsestate(pstate); @@ -4168,7 +4168,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, if (!IsA(querytree, Query) || querytree->commandType != CMD_SELECT || querytree->utilityStmt || - querytree->intoClause || querytree->hasAggs || querytree->hasWindowFuncs || querytree->hasSubLinks || @@ -4678,12 +4677,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) querytree = linitial(querytree_list); /* - * The single command must be a regular results-returning SELECT. + * The single command must be a plain SELECT. */ if (!IsA(querytree, Query) || querytree->commandType != CMD_SELECT || - querytree->utilityStmt || - querytree->intoClause) + querytree->utilityStmt) goto fail; /* diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index b187b03666..3329e9c964 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -10,8 +10,8 @@ * utility commands, no locks are obtained here (and if they were, we could * not be sure we'd still have them at execution). Hence the general rule * for utility commands is to just dump them into a Query node untransformed. - * DECLARE CURSOR and EXPLAIN are exceptions because they contain - * optimizable statements. + * DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they + * contain optimizable statements, which we should transform. * * * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group @@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt); static Query *transformExplainStmt(ParseState *pstate, ExplainStmt *stmt); +static Query *transformCreateTableAsStmt(ParseState *pstate, + CreateTableAsStmt *stmt); static void transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, bool pushedDown); @@ -91,7 +93,7 @@ parse_analyze(Node *parseTree, const char *sourceText, if (numParams > 0) parse_fixed_parameters(pstate, paramTypes, numParams); - query = transformStmt(pstate, parseTree); + query = transformTopLevelStmt(pstate, parseTree); free_parsestate(pstate); @@ -118,7 +120,7 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText, parse_variable_parameters(pstate, paramTypes, numParams); - query = transformStmt(pstate, parseTree); + query = transformTopLevelStmt(pstate, parseTree); /* make sure all is well with parameter types */ check_variable_parameters(pstate, query); @@ -151,8 +153,52 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState, } /* - * transformStmt - + * transformTopLevelStmt - * transform a Parse tree into a Query tree. + * + * The only thing we do here that we don't do in transformStmt() is to + * convert SELECT ... INTO into CREATE TABLE AS. Since utility statements + * aren't allowed within larger statements, this is only allowed at the top + * of the parse tree, and so we only try it before entering the recursive + * transformStmt() processing. + */ +Query * +transformTopLevelStmt(ParseState *pstate, Node *parseTree) +{ + if (IsA(parseTree, SelectStmt)) + { + SelectStmt *stmt = (SelectStmt *) parseTree; + + /* If it's a set-operation tree, drill down to leftmost SelectStmt */ + while (stmt && stmt->op != SETOP_NONE) + stmt = stmt->larg; + Assert(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL); + + if (stmt->intoClause) + { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + + ctas->query = parseTree; + ctas->into = stmt->intoClause; + ctas->is_select_into = true; + + /* + * Remove the intoClause from the SelectStmt. This makes it safe + * for transformSelectStmt to complain if it finds intoClause set + * (implying that the INTO appeared in a disallowed place). + */ + stmt->intoClause = NULL; + + parseTree = (Node *) ctas; + } + } + + return transformStmt(pstate, parseTree); +} + +/* + * transformStmt - + * recursively transform a Parse tree into a Query tree. */ Query * transformStmt(ParseState *pstate, Node *parseTree) @@ -202,6 +248,11 @@ transformStmt(ParseState *pstate, Node *parseTree) (ExplainStmt *) parseTree); break; + case T_CreateTableAsStmt: + result = transformCreateTableAsStmt(pstate, + (CreateTableAsStmt *) parseTree); + break; + default: /* @@ -258,6 +309,7 @@ analyze_requires_snapshot(Node *parseTree) break; case T_ExplainStmt: + case T_CreateTableAsStmt: /* yes, because we must analyze the contained statement */ result = true; break; @@ -459,17 +511,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) free_parsestate(sub_pstate); - /* The grammar should have produced a SELECT, but it might have INTO */ + /* The grammar should have produced a SELECT */ if (!IsA(selectQuery, Query) || selectQuery->commandType != CMD_SELECT || selectQuery->utilityStmt != NULL) elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT"); - if (selectQuery->intoClause) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("INSERT ... SELECT cannot specify INTO"), - parser_errposition(pstate, - exprLocation((Node *) selectQuery->intoClause)))); /* * Make the source be a subquery in the INSERT's rangetable, and add @@ -539,6 +585,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) int sublist_length = -1; int i; + Assert(selectStmt->intoClause == NULL); + foreach(lc, selectStmt->valuesLists) { List *sublist = (List *) lfirst(lc); @@ -653,6 +701,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) List *valuesLists = selectStmt->valuesLists; Assert(list_length(valuesLists) == 1); + Assert(selectStmt->intoClause == NULL); /* Do basic expression transformation (same as a ROW() expr) */ exprList = transformExpressionList(pstate, @@ -886,6 +935,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } + /* Complain if we get called from someplace where INTO is not allowed */ + if (stmt->intoClause) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SELECT ... INTO is not allowed here"), + parser_errposition(pstate, + exprLocation((Node *) stmt->intoClause)))); + /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */ pstate->p_locking_clause = stmt->lockingClause; @@ -963,9 +1020,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) pstate->p_windowdefs, &qry->targetList); - /* SELECT INTO/CREATE TABLE AS spec is just passed through */ - qry->intoClause = stmt->intoClause; - qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); @@ -1013,6 +1067,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) /* Most SELECT stuff doesn't apply in a VALUES clause */ Assert(stmt->distinctClause == NIL); + Assert(stmt->intoClause == NULL); Assert(stmt->targetList == NIL); Assert(stmt->fromClause == NIL); Assert(stmt->whereClause == NULL); @@ -1185,9 +1240,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"))); - /* CREATE TABLE AS spec is just passed through */ - qry->intoClause = stmt->intoClause; - /* * There mustn't have been any table references in the expressions, else * strange things would happen, like Cartesian products of those tables @@ -1286,21 +1338,27 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) } /* - * Find leftmost leaf SelectStmt; extract the one-time-only items from it - * and from the top-level node. + * Find leftmost leaf SelectStmt. We currently only need to do this in + * order to deliver a suitable error message if there's an INTO clause + * there, implying the set-op tree is in a context that doesn't allow + * INTO. (transformSetOperationTree would throw error anyway, but it + * seems worth the trouble to throw a different error for non-leftmost + * INTO, so we produce that error in transformSetOperationTree.) */ leftmostSelect = stmt->larg; while (leftmostSelect && leftmostSelect->op != SETOP_NONE) leftmostSelect = leftmostSelect->larg; Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) && leftmostSelect->larg == NULL); - qry->intoClause = leftmostSelect->intoClause; - - /* clear this to prevent complaints in transformSetOperationTree() */ - leftmostSelect->intoClause = NULL; + if (leftmostSelect->intoClause) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SELECT ... INTO is not allowed here"), + parser_errposition(pstate, + exprLocation((Node *) leftmostSelect->intoClause)))); /* - * These are not one-time, exactly, but we want to process them here and + * We need to extract ORDER BY and other top-level clauses here and * not let transformSetOperationTree() see them --- else it'll just * recurse right back here! */ @@ -2107,14 +2165,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) result->utilityStmt != NULL) elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR"); - /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */ - if (result->intoClause) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("DECLARE CURSOR cannot specify INTO"), - parser_errposition(pstate, - exprLocation((Node *) result->intoClause)))); - /* * We also disallow data-modifying WITH in a cursor. (This could be * allowed, but the semantics of when the updates occur might be @@ -2170,6 +2220,29 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) { Query *result; + /* transform contained query, allowing SELECT INTO */ + stmt->query = (Node *) transformTopLevelStmt(pstate, stmt->query); + + /* represent the command as a utility Query */ + result = makeNode(Query); + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + return result; +} + + +/* + * transformCreateTableAsStmt - + * transform a CREATE TABLE AS (or SELECT ... INTO) Statement + * + * As with EXPLAIN, transform the contained statement now. + */ +static Query * +transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) +{ + Query *result; + /* transform contained query */ stmt->query = (Node *) transformStmt(pstate, stmt->query); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index feb28a4172..3827e2e1ad 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -124,7 +124,6 @@ static void check_qualified_name(List *names, core_yyscan_t yyscanner); static List *check_func_name(List *names, core_yyscan_t yyscanner); static List *check_indirection(List *indirection, core_yyscan_t yyscanner); static List *extractArgTypes(List *parameters); -static SelectStmt *findLeftmostSelect(SelectStmt *node); static void insertSelectOptions(SelectStmt *stmt, List *sortClause, List *lockingClause, Node *limitOffset, Node *limitCount, @@ -3044,31 +3043,27 @@ ExistingIndex: USING INDEX index_name { $$ = $3; } ; -/* - * Note: CREATE TABLE ... AS SELECT ... is just another spelling for - * SELECT ... INTO. - */ +/***************************************************************************** + * + * QUERY : + * CREATE TABLE relname AS SelectStmt [ WITH [NO] DATA ] + * + * + * Note: SELECT ... INTO is a now-deprecated alternative for this. + * + *****************************************************************************/ CreateAsStmt: CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data { - /* - * When the SelectStmt is a set-operation tree, we must - * stuff the INTO information into the leftmost component - * Select, because that's where analyze.c will expect - * to find it. - */ - SelectStmt *n = findLeftmostSelect((SelectStmt *) $6); - if (n->intoClause != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE TABLE AS cannot specify INTO"), - parser_errposition(exprLocation((Node *) n->intoClause)))); - n->intoClause = $4; + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); + ctas->query = $6; + ctas->into = $4; + ctas->is_select_into = false; /* cram additional flags into the IntoClause */ $4->rel->relpersistence = $2; $4->skipData = !($7); - $$ = $6; + $$ = (Node *) ctas; } ; @@ -8285,20 +8280,22 @@ ExecuteStmt: EXECUTE name execute_param_clause ExecuteStmt *n = makeNode(ExecuteStmt); n->name = $2; n->params = $3; - n->into = NULL; $$ = (Node *) n; } | CREATE OptTemp TABLE create_as_target AS EXECUTE name execute_param_clause opt_with_data { + CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt); ExecuteStmt *n = makeNode(ExecuteStmt); n->name = $7; n->params = $8; - n->into = $4; + ctas->query = (Node *) n; + ctas->into = $4; + ctas->is_select_into = false; /* cram additional flags into the IntoClause */ $4->rel->relpersistence = $2; $4->skipData = !($9); - $$ = (Node *) n; + $$ = (Node *) ctas; } ; @@ -12870,18 +12867,6 @@ extractArgTypes(List *parameters) return result; } -/* findLeftmostSelect() - * Find the leftmost component SelectStmt in a set-operation parsetree. - */ -static SelectStmt * -findLeftmostSelect(SelectStmt *node) -{ - while (node && node->op != SETOP_NONE) - node = node->larg; - Assert(node && IsA(node, SelectStmt) && node->larg == NULL); - return node; -} - /* insertSelectOptions() * Insert ORDER BY, etc into an already-constructed SelectStmt. * diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 7f4da921b2..3a23cddd33 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -495,12 +495,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) query->commandType != CMD_SELECT || query->utilityStmt != NULL) elog(ERROR, "unexpected non-SELECT command in subquery in FROM"); - if (query->intoClause) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery in FROM cannot have SELECT INTO"), - parser_errposition(pstate, - exprLocation((Node *) query->intoClause)))); /* * The subquery cannot make use of any variables from FROM items created diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index c0c6240103..2a7d4cd072 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -253,13 +253,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) if (query->utilityStmt != NULL) elog(ERROR, "unexpected utility statement in WITH"); - if (query->intoClause) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery in WITH cannot have SELECT INTO"), - parser_errposition(pstate, - exprLocation((Node *) query->intoClause)))); - /* * We disallow data-modifying WITH except at the top level of a query, * because it's not clear when such a modification should be executed. diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index d22d8a12ba..973265bcb0 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1408,12 +1408,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink) qtree->commandType != CMD_SELECT || qtree->utilityStmt != NULL) elog(ERROR, "unexpected non-SELECT command in SubLink"); - if (qtree->intoClause) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery cannot have SELECT INTO"), - parser_errposition(pstate, - exprLocation((Node *) qtree->intoClause)))); sublink->subselect = (Node *) qtree; diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 645182dbfa..6e5633dcdb 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/dest.c b/src/backend/tcop/dest.c index 943b6c141c..c6ab54aa3b 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -31,7 +31,7 @@ #include "access/printtup.h" #include "access/xact.h" #include "commands/copy.h" -#include "executor/executor.h" +#include "commands/createas.h" #include "executor/functions.h" #include "executor/tstoreReceiver.h" #include "libpq/libpq.h" @@ -118,7 +118,7 @@ CreateDestReceiver(CommandDest dest) return CreateTuplestoreDestReceiver(); case DestIntoRel: - return CreateIntoRelDestReceiver(); + return CreateIntoRelDestReceiver(NULL); case DestCopyOut: return CreateCopyDestReceiver(); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 397c0734c2..14ca768139 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -624,7 +624,7 @@ pg_analyze_and_rewrite_params(Node *parsetree, pstate->p_sourcetext = query_string; (*parserSetup) (pstate, parserSetupArg); - query = transformStmt(pstate, parsetree); + query = transformTopLevelStmt(pstate, parsetree); free_parsestate(pstate); @@ -975,7 +975,7 @@ exec_simple_query(const char *query_string) * end up being able to do this, keeping the parse/plan snapshot around * until after we start the portal doesn't cost much. */ - PortalStart(portal, NULL, snapshot_set); + PortalStart(portal, NULL, 0, snapshot_set); /* Done with the snapshot used for parsing/planning */ if (snapshot_set) @@ -1709,7 +1709,7 @@ exec_bind_message(StringInfo input_message) * for query execution (currently, reuse will only occur if * PORTAL_ONE_SELECT mode is chosen). */ - PortalStart(portal, params, snapshot_set); + PortalStart(portal, params, 0, snapshot_set); /* Done with the snapshot used for parameter I/O and parsing/planning */ if (snapshot_set) diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 42a0fb0f1f..d0db7ad62c 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); } @@ -442,9 +437,15 @@ FetchStatementTargetList(Node *stmt) * Prepare a portal for execution. * * Caller must already have created the portal, done PortalDefineQuery(), - * and adjusted portal options if needed. If parameters are needed by - * the query, they must be passed in here (caller is responsible for - * giving them appropriate lifetime). + * and adjusted portal options if needed. + * + * If parameters are needed by the query, they must be passed in "params" + * (caller is responsible for giving them appropriate lifetime). + * + * The caller can also provide an initial set of "eflags" to be passed to + * ExecutorStart (but note these can be modified internally, and they are + * currently only honored for PORTAL_ONE_SELECT portals). Most callers + * should simply pass zero. * * The use_active_snapshot parameter is currently used only for * PORTAL_ONE_SELECT portals. If it is true, the active snapshot will @@ -456,14 +457,15 @@ FetchStatementTargetList(Node *stmt) * tupdesc (if any) is known. */ void -PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot) +PortalStart(Portal portal, ParamListInfo params, + int eflags, bool use_active_snapshot) { Portal saveActivePortal; ResourceOwner saveResourceOwner; MemoryContext savePortalContext; MemoryContext oldContext; QueryDesc *queryDesc; - int eflags; + int myeflags; AssertArg(PortalIsValid(portal)); AssertState(portal->status == PORTAL_DEFINED); @@ -517,17 +519,18 @@ PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot) /* * If it's a scrollable cursor, executor needs to support - * REWIND and backwards scan. + * REWIND and backwards scan, as well as whatever the caller + * might've asked for. */ if (portal->cursorOptions & CURSOR_OPT_SCROLL) - eflags = EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD; + myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD; else - eflags = 0; /* default run-to-completion flags */ + myeflags = eflags; /* * Call ExecutorStart to prepare the plan for execution */ - ExecutorStart(queryDesc, eflags); + ExecutorStart(queryDesc, myeflags); /* * This tells PortalCleanup to shut down the executor diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5b81c0bbed..ea2a6c6a08 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -29,6 +29,7 @@ #include "commands/collationcmds.h" #include "commands/conversioncmds.h" #include "commands/copy.h" +#include "commands/createas.h" #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/discard.h" @@ -127,9 +128,7 @@ CommandIsReadOnly(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->intoClause != NULL) - return false; /* SELECT INTO */ - else if (stmt->rowMarks != NIL) + if (stmt->rowMarks != NIL) return false; /* SELECT FOR UPDATE/SHARE */ else if (stmt->hasModifyingCTE) return false; /* data-modifying CTE */ @@ -198,6 +197,7 @@ check_xact_readonly(Node *parsetree) case T_CreateSchemaStmt: case T_CreateSeqStmt: case T_CreateStmt: + case T_CreateTableAsStmt: case T_CreateTableSpaceStmt: case T_CreateTrigStmt: case T_CompositeTypeStmt: @@ -673,7 +673,8 @@ standard_ProcessUtility(Node *parsetree, break; case T_ExecuteStmt: - ExecuteQuery((ExecuteStmt *) parsetree, queryString, params, + ExecuteQuery((ExecuteStmt *) parsetree, NULL, + queryString, params, dest, completionTag); break; @@ -1036,6 +1037,11 @@ standard_ProcessUtility(Node *parsetree, ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest); break; + case T_CreateTableAsStmt: + ExecCreateTableAs((CreateTableAsStmt *) parsetree, + queryString, params, completionTag); + break; + case T_VariableSetStmt: ExecSetVariableStmt((VariableSetStmt *) parsetree); break; @@ -1230,8 +1236,6 @@ UtilityReturnsTuples(Node *parsetree) ExecuteStmt *stmt = (ExecuteStmt *) parsetree; PreparedStatement *entry; - if (stmt->into) - return false; entry = FetchPreparedStatement(stmt->name, false); if (!entry) return false; /* not our business to raise error */ @@ -1282,8 +1286,6 @@ UtilityTupleDescriptor(Node *parsetree) ExecuteStmt *stmt = (ExecuteStmt *) parsetree; PreparedStatement *entry; - if (stmt->into) - return NULL; entry = FetchPreparedStatement(stmt->name, false); if (!entry) return NULL; /* not our business to raise error */ @@ -1317,9 +1319,8 @@ QueryReturnsTuples(Query *parsetree) switch (parsetree->commandType) { case CMD_SELECT: - /* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */ - if (parsetree->utilityStmt == NULL && - parsetree->intoClause == NULL) + /* returns tuples ... unless it's DECLARE CURSOR */ + if (parsetree->utilityStmt == NULL) return true; break; case CMD_INSERT: @@ -1341,6 +1342,37 @@ QueryReturnsTuples(Query *parsetree) #endif +/* + * UtilityContainsQuery + * Return the contained Query, or NULL if there is none + * + * Certain utility statements, such as EXPLAIN, contain a Query. + * This function encapsulates knowledge of exactly which ones do. + * We assume it is invoked only on already-parse-analyzed statements + * (else the contained parsetree isn't a Query yet). + */ +Query * +UtilityContainsQuery(Node *parsetree) +{ + switch (nodeTag(parsetree)) + { + case T_ExplainStmt: + Assert(IsA(((ExplainStmt *) parsetree)->query, Query)); + return (Query *) ((ExplainStmt *) parsetree)->query; + + case T_CreateTableAsStmt: + /* might or might not contain a Query ... */ + if (IsA(((CreateTableAsStmt *) parsetree)->query, Query)) + return (Query *) ((CreateTableAsStmt *) parsetree)->query; + Assert(IsA(((CreateTableAsStmt *) parsetree)->query, ExecuteStmt)); + return NULL; + + default: + return NULL; + } +} + + /* * AlterObjectTypeCommandTag * helper function for CreateCommandTag @@ -1907,6 +1939,13 @@ CreateCommandTag(Node *parsetree) tag = "EXPLAIN"; break; + case T_CreateTableAsStmt: + if (((CreateTableAsStmt *) parsetree)->is_select_into) + tag = "SELECT INTO"; + else + tag = "CREATE TABLE AS"; + break; + case T_VariableSetStmt: switch (((VariableSetStmt *) parsetree)->kind) { @@ -2060,8 +2099,6 @@ CreateCommandTag(Node *parsetree) Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); tag = "DECLARE CURSOR"; } - else if (stmt->intoClause != NULL) - tag = "SELECT INTO"; else if (stmt->rowMarks != NIL) { /* not 100% but probably close enough */ @@ -2110,8 +2147,6 @@ CreateCommandTag(Node *parsetree) Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); tag = "DECLARE CURSOR"; } - else if (stmt->intoClause != NULL) - tag = "SELECT INTO"; else if (stmt->rowMarks != NIL) { /* not 100% but probably close enough */ @@ -2179,7 +2214,7 @@ GetCommandLogLevel(Node *parsetree) case T_SelectStmt: if (((SelectStmt *) parsetree)->intoClause) - lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ + lev = LOGSTMT_DDL; /* SELECT INTO */ else lev = LOGSTMT_ALL; break; @@ -2429,6 +2464,10 @@ GetCommandLogLevel(Node *parsetree) } break; + case T_CreateTableAsStmt: + lev = LOGSTMT_DDL; + break; + case T_VariableSetStmt: lev = LOGSTMT_ALL; break; @@ -2529,10 +2568,7 @@ GetCommandLogLevel(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->intoClause != NULL) - lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ - else - lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */ + lev = LOGSTMT_ALL; break; case CMD_UPDATE: @@ -2558,10 +2594,7 @@ GetCommandLogLevel(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->intoClause != NULL) - lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ - else - lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */ + lev = LOGSTMT_ALL; break; case CMD_UPDATE: diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c index 7e66645b2b..6292f8dc6c 100644 --- a/src/backend/utils/cache/plancache.c +++ b/src/backend/utils/cache/plancache.c @@ -1232,20 +1232,16 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) if (!IsA(plannedstmt, PlannedStmt)) { /* - * Ignore utility statements, except EXPLAIN which contains a - * parsed-but-not-planned query. Note: it's okay to use + * Ignore utility statements, except those (such as EXPLAIN) that + * contain a parsed-but-not-planned query. Note: it's okay to use * ScanQueryForLocks, even though the query hasn't been through * rule rewriting, because rewriting doesn't change the query * representation. */ - if (IsA(plannedstmt, ExplainStmt)) - { - Query *query; + Query *query = UtilityContainsQuery((Node *) plannedstmt); - query = (Query *) ((ExplainStmt *) plannedstmt)->query; - Assert(IsA(query, Query)); + if (query) ScanQueryForLocks(query, acquire); - } continue; } @@ -1304,13 +1300,10 @@ AcquirePlannerLocks(List *stmt_list, bool acquire) if (query->commandType == CMD_UTILITY) { - /* Ignore utility statements, except EXPLAIN */ - if (IsA(query->utilityStmt, ExplainStmt)) - { - query = (Query *) ((ExplainStmt *) query->utilityStmt)->query; - Assert(IsA(query, Query)); + /* Ignore utility statements, unless they contain a Query */ + query = UtilityContainsQuery(query->utilityStmt); + if (query) ScanQueryForLocks(query, acquire); - } continue; } @@ -1648,9 +1641,9 @@ ResetPlanCache(void) * aborted transactions when we can't revalidate them (cf bug #5269). * In general there is no point in invalidating utility statements * since they have no plans anyway. So invalidate it only if it - * contains at least one non-utility statement. (EXPLAIN counts as a - * non-utility statement, though, since it contains an analyzed query - * that might have dependencies.) + * contains at least one non-utility statement, or contains a utility + * statement that contains a pre-analyzed query (which could have + * dependencies.) */ foreach(lc, plansource->query_list) { @@ -1658,12 +1651,13 @@ ResetPlanCache(void) Assert(IsA(query, Query)); if (query->commandType != CMD_UTILITY || - IsA(query->utilityStmt, ExplainStmt)) + UtilityContainsQuery(query->utilityStmt)) { /* non-utility statement, so invalidate */ plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; + /* no need to look further */ break; } } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 59fd53d2c5..5d896bd11a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201203111 +#define CATALOG_VERSION_NO 201203191 #endif diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h new file mode 100644 index 0000000000..ed65ccd8ee --- /dev/null +++ b/src/include/commands/createas.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * createas.h + * prototypes for createas.c. + * + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/createas.h + * + *------------------------------------------------------------------------- + */ +#ifndef CREATEAS_H +#define CREATEAS_H + +#include "nodes/params.h" +#include "nodes/parsenodes.h" +#include "tcop/dest.h" + + +extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, + ParamListInfo params, char *completionTag); + +extern int GetIntoRelEFlags(IntoClause *intoClause); + +extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause); + +#endif /* CREATEAS_H */ diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index a987c43124..e4e98bfb04 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -42,6 +42,7 @@ typedef struct ExplainState /* Hook for plugins to get control in ExplainOneQuery() */ typedef void (*ExplainOneQuery_hook_type) (Query *query, + IntoClause *into, ExplainState *es, const char *queryString, ParamListInfo params); @@ -59,10 +60,12 @@ extern void ExplainInitState(ExplainState *es); extern TupleDesc ExplainResultDesc(ExplainStmt *stmt); -extern void ExplainOneUtility(Node *utilityStmt, ExplainState *es, +extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into, + ExplainState *es, const char *queryString, ParamListInfo params); -extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, +extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, + ExplainState *es, const char *queryString, ParamListInfo params); extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 472a357c29..f688be77a3 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -35,11 +35,12 @@ typedef struct /* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */ extern void PrepareQuery(PrepareStmt *stmt, const char *queryString); -extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString, - ParamListInfo params, +extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, + const char *queryString, ParamListInfo params, DestReceiver *dest, char *completionTag); extern void DeallocateQuery(DeallocateStmt *stmt); -extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, +extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, + ExplainState *es, const char *queryString, ParamListInfo params); /* Low-level access to stored prepared statements */ @@ -52,6 +53,6 @@ extern void DropPreparedStatement(const char *stmt_name, bool showError); extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt); extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt); -void DropAllPreparedStatements(void); +extern void DropAllPreparedStatements(void); #endif /* PREPARE_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 7f27669571..f5503a5663 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -30,8 +30,8 @@ * * EXPLAIN_ONLY indicates that the plan tree is being initialized just so * EXPLAIN can print it out; it will not be run. Hence, no side-effects - * of startup should occur (such as creating a SELECT INTO target table). - * However, error checks (such as permission checks) should be performed. + * of startup should occur. However, error checks (such as permission checks) + * should be performed. * * REWIND indicates that the plan node should try to efficiently support * rescans without parameter changes. (Nodes must support ExecReScan calls @@ -49,12 +49,18 @@ * AfterTriggerBeginQuery/AfterTriggerEndQuery. This does not necessarily * mean that the plan can't queue any AFTER triggers; just that the caller * is responsible for there being a trigger context for them to be queued in. + * + * WITH/WITHOUT_OIDS tell the executor to emit tuples with or without space + * for OIDs, respectively. These are currently used only for CREATE TABLE AS. + * If neither is set, the plan may or may not produce tuples including OIDs. */ #define EXEC_FLAG_EXPLAIN_ONLY 0x0001 /* EXPLAIN, no ANALYZE */ #define EXEC_FLAG_REWIND 0x0002 /* need efficient rescan */ #define EXEC_FLAG_BACKWARD 0x0004 /* need backward scan */ #define EXEC_FLAG_MARK 0x0008 /* need mark/restore */ #define EXEC_FLAG_SKIP_TRIGGERS 0x0010 /* skip AfterTrigger calls */ +#define EXEC_FLAG_WITH_OIDS 0x0020 /* force OIDs in returned tuples */ +#define EXEC_FLAG_WITHOUT_OIDS 0x0040 /* force no OIDs in returned tuples */ /* @@ -204,7 +210,6 @@ extern void EvalPlanQualFetchRowMarks(EPQState *epqstate); extern TupleTableSlot *EvalPlanQualNext(EPQState *epqstate); extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate); extern void EvalPlanQualEnd(EPQState *epqstate); -extern DestReceiver *CreateIntoRelDestReceiver(void); /* * prototypes from functions in execProcnode.c diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 5207102f6c..b48a03b4b2 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -371,8 +371,6 @@ typedef struct EState int es_top_eflags; /* eflags passed to ExecutorStart */ int es_instrument; /* OR of InstrumentOption flags */ - bool es_select_into; /* true if doing SELECT INTO */ - bool es_into_oids; /* true to generate OIDs in SELECT INTO */ bool es_finished; /* true when ExecutorFinish is done */ List *es_exprcontexts; /* List of ExprContexts within EState */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 905458fd50..fdcc80edbb 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -303,6 +303,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 ab5563997d..07a1ab7550 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -84,7 +84,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ /* * Query - - * Parse analysis turns all statements into a Query tree (via transformStmt) + * Parse analysis turns all statements into a Query tree * for further processing by the rewriter and planner. * * Utility statements (i.e. non-optimizable statements) have the @@ -111,8 +111,6 @@ typedef struct Query int resultRelation; /* rtable index of target relation for * INSERT/UPDATE/DELETE; 0 for SELECT */ - IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */ - bool hasAggs; /* has aggregates in tlist or havingQual */ bool hasWindowFuncs; /* has window functions in tlist */ bool hasSubLinks; /* has subquery SubLink */ @@ -1009,7 +1007,7 @@ typedef struct SelectStmt */ List *distinctClause; /* NULL, list of DISTINCT ON exprs, or * lcons(NIL,NIL) for all (SELECT DISTINCT) */ - IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */ + IntoClause *intoClause; /* target for SELECT INTO */ List *targetList; /* the target list (of ResTarget) */ List *fromClause; /* the FROM clause */ Node *whereClause; /* WHERE qualification */ @@ -2395,6 +2393,25 @@ typedef struct ExplainStmt List *options; /* list of DefElem nodes */ } ExplainStmt; +/* ---------------------- + * CREATE TABLE AS Statement (a/k/a SELECT INTO) + * + * A query written as CREATE TABLE AS will produce this node type natively. + * A query written as SELECT ... INTO will be transformed to this form during + * parse analysis. + * + * The "query" field is handled similarly to EXPLAIN, though note that it + * can be a SELECT or an EXECUTE, but not other DML statements. + * ---------------------- + */ +typedef struct CreateTableAsStmt +{ + NodeTag type; + Node *query; /* the query (see comments above) */ + IntoClause *into; /* destination table */ + bool is_select_into; /* it was written as SELECT INTO */ +} CreateTableAsStmt; + /* ---------------------- * Checkpoint Statement * ---------------------- @@ -2509,7 +2526,6 @@ typedef struct ExecuteStmt { NodeTag type; char *name; /* The name of the plan to execute */ - IntoClause *into; /* Optional table to store results in */ List *params; /* Values to assign to parameters */ } ExecuteStmt; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index e6bb3239f4..c7c1a154fc 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/include/parser/analyze.h b/src/include/parser/analyze.h index b8987db214..8367db8b8c 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -25,6 +25,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText, extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState, CommonTableExpr *parentCTE, bool locked_from_parent); + +extern Query *transformTopLevelStmt(ParseState *pstate, Node *parseTree); extern Query *transformStmt(ParseState *pstate, Node *parseTree); extern bool analyze_requires_snapshot(Node *parseTree); diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index 98b825a106..22aad2e96c 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -28,7 +28,7 @@ extern List *FetchPortalTargetList(Portal portal); extern List *FetchStatementTargetList(Node *stmt); extern void PortalStart(Portal portal, ParamListInfo params, - bool use_active_snapshot); + int eflags, bool use_active_snapshot); extern void PortalSetResultFormat(Portal portal, int nFormats, int16 *formats); diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index 14f0fa8e9d..54190b2f6c 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -34,6 +34,8 @@ extern bool UtilityReturnsTuples(Node *parsetree); extern TupleDesc UtilityTupleDescriptor(Node *parsetree); +extern Query *UtilityContainsQuery(Node *parsetree); + extern const char *CreateCommandTag(Node *parsetree); extern LogStmtLevel GetCommandLogLevel(Node *parsetree); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 0b126a3f4a..09c86aa4c6 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3291,24 +3291,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, * We want to disallow SELECT INTO for now, because its behavior * is not consistent with SELECT INTO in a normal plpgsql context. * (We need to reimplement EXECUTE to parse the string as a - * plpgsql command, not just feed it to SPI_execute.) However, - * CREATE AS should be allowed ... and since it produces the same - * parsetree as SELECT INTO, there's no way to tell the difference - * except to look at the source text. Wotta kluge! + * plpgsql command, not just feed it to SPI_execute.) This is not + * a functional limitation because CREATE TABLE AS is allowed. */ - { - char *ptr; - - for (ptr = querystr; *ptr; ptr++) - if (!scanner_isspace(*ptr)) - break; - if (*ptr == 'S' || *ptr == 's') - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("EXECUTE of SELECT ... INTO is not implemented"), - errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."))); - break; - } + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("EXECUTE of SELECT ... INTO is not implemented"), + errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."))); + break; /* Some SPI errors deserve specific error messages */ case SPI_ERROR_COPY: @@ -5759,7 +5749,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr) */ if (!IsA(query, Query)) return; - if (query->commandType != CMD_SELECT || query->intoClause) + if (query->commandType != CMD_SELECT) return; if (query->rtable != NIL) return; @@ -5833,7 +5823,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan) */ if (!IsA(stmt, PlannedStmt)) return; - if (stmt->commandType != CMD_SELECT || stmt->intoClause) + if (stmt->commandType != CMD_SELECT) return; plan = stmt->planTree; if (!IsA(plan, Result)) diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/expected/select_into.out index c8327f677a..9d3f04758e 100644 --- a/src/test/regress/expected/select_into.out +++ b/src/test/regress/expected/select_into.out @@ -75,3 +75,22 @@ SELECT * FROM created_table; (5 rows) DROP TABLE created_table; +-- +-- Disallowed uses of SELECT ... INTO. All should fail +-- +DECLARE foo CURSOR FOR SELECT 1 INTO b; +ERROR: SELECT ... INTO is not allowed here +LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b; + ^ +COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob'; +ERROR: COPY (SELECT INTO) is not supported +SELECT * FROM (SELECT 1 INTO f) bar; +ERROR: SELECT ... INTO is not allowed here +LINE 1: SELECT * FROM (SELECT 1 INTO f) bar; + ^ +CREATE VIEW foo AS SELECT 1 INTO b; +ERROR: views must not contain SELECT INTO +INSERT INTO b SELECT 1 INTO f; +ERROR: SELECT ... INTO is not allowed here +LINE 1: INSERT INTO b SELECT 1 INTO f; + ^ diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index e9d3908b1a..6981692da9 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 09d210bc6f..4d1cc86556 100644 --- a/src/test/regress/sql/select_into.sql +++ b/src/test/regress/sql/select_into.sql @@ -67,3 +67,12 @@ SELECT make_table(); SELECT * FROM created_table; DROP TABLE created_table; + +-- +-- Disallowed uses of SELECT ... INTO. All should fail +-- +DECLARE foo CURSOR FOR SELECT 1 INTO b; +COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob'; +SELECT * FROM (SELECT 1 INTO f) bar; +CREATE VIEW foo AS SELECT 1 INTO b; +INSERT INTO b SELECT 1 INTO f;