diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 4af7aad00b5b8d5d89e99da625096bc5174a7475..9573a0db45be9c04d8a0e9cc4acada21caf6c18b 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 517660d3735d75724e1038c14823ab13b6f81ce4..6b5bcd83c5bbcac9234d727734499aa63a0a7a49 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 0000000000000000000000000000000000000000..5173f5a3081e6c6a161ec3801f86e8fa2545780d --- /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 93b1f34ca0c625ec70a7ca4baeebd41d4f189a1d..a14cae144205a86956289e2f74c78350a8960553 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 1c7a1c3a33f44caa0d79fafb908734c4a59bd54d..e402042332569b5599de18fd7171c11e65038298 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 4883abe470ed93922fb10515c416fe1dd9e9ecca..edd646e7c348f01ff4304c57aaa3873af2b65f09 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 99fb7dbb8f4d0aa2dd6e10883cbfb1cc9b333c53..c887961bc9751c4ec3296573fbcec8fef37f5c16 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 36dcc8e4b5d2e24abca9f07b8dcd673200ca745d..fbb36fa6dc460654a715213d55dbcbdddefec893 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 6db42e7b9709fe84246b26f285d94949dab2301d..40cd5ce5d19d627141549769233b2c6215fda7bb 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 61f462254ff55cc0bfed5be2db486e1257baa527..ae8d374db216328f6db9eebe3a0f92ea18f93864 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 81f284ca0446fc9a099f68d90cac2dff7a1a5cd4..5e4ae426b1ba03ef97317dd83271bc8e757180ff 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 5cde22543f5b7d4d607224acfa22a604a419ed63..cf23b0887242f4d3a008599b5464bade86cc412b 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 d2a79eb851c8cf7134a6f0decc7045de4f667c93..5e691f96f79ac1e0a4177ab3edd42b3ab265d895 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 51181a9a7438e8609ab922340d1c2d20ba73726d..e925434eb3943fa5dda043d45141e8fd21b9a658 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 b9258ad70e8effa1f1797272384facd27c295737..9b579560c5e855aaaa89074eb506c9430e9db855 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 8bbe97713bb13cc4d3c6fba638c2d771ee5901b3..6b0541b9b59346d5166e88ba78e5b5172ac6edaf 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 69396694aaa9df0edbc361ede98b3f77db6724dc..9e347ce7360c8d8d41124dd6e20488b3c267dc12 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 b64db1e1c0659ea9b6af25327f689b083de845e6..f30f02f266d2596942d89b3e3c7b8d4d6ff7fe2c 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 2fd795b45929014257930edd4af620e7e8d7a5a7..c1b2f1db11adc7785db84b6e9da8cebae753ee3a 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 cd3da46bc5e27e29c2eb04f14745ed9777316402..b14ae2e3670f2f41a03c1688a4186beb1b3dd955 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 b187b03666a30ff40825a34c240a08a13f7789c2..3329e9c964548e40a84397e8c26135f1cae74c58 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 feb28a41720f20d44ee3eb8fb666772203b1ed9d..3827e2e1add11a0387a9dc7ca11439404e76196c 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 7f4da921b2f0ff906057c43e57066d728548feee..3a23cddd3378e31651f064b0765de0429c8b3907 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 c0c624010379fe8a317626a6882c1043b82a69ab..2a7d4cd07244054a9e3cea3554712eb133662384 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 d22d8a12bac802d45479bcdb1e810996d4cad916..973265bcb0b1af7b3411b3f1a54aee27d790297c 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 645182dbfa4668450b35e3ba73bd2f3167e1eb52..6e5633dcdb5200c5bce73936c6938d0a11b36908 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 943b6c141cadc03eaad7976a265eba88e5bb772d..c6ab54aa3bb5625f93f21d6afda3813edc3344f6 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 397c0734c2900501b443bf16f2a7296c2d09aa88..14ca7681392e93a6679cadf7a9c36108663f48e8 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 42a0fb0f1f26e400b7949b0be0a388d6b290d38d..d0db7ad62c23bebc72427bef6742f8da6787d50c 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 5b81c0bbedcf324f6832e856c8bc8d15e2f26c24..ea2a6c6a0822c5860278f9371dd18e945d7ae116 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 7e66645b2b61d18552bb30ae5487f42a555fad50..6292f8dc6c99a55e81b4bdd781acdbd1ff96e3cb 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 59fd53d2c5fd73a246ac702f58172347a73b4a48..5d896bd11ae019aff70867379e16d06fddbe7d7d 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 0000000000000000000000000000000000000000..ed65ccd8ee3fde3f2198bec39df25387853d3f11 --- /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 a987c4312420a2bbbf277b58e7266f03c989e85d..e4e98bfb0435ad05b9fc2f515f5d0ad93bcd1479 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 472a357c2998893cdc6c8e678760c5c67f821cf6..f688be77a322d72e670194c4089e613a6bf9d736 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 7f27669571243f111105e6558553e814cd6ff0d3..f5503a566341550b5d49b3c31eb0a3bb7aee9513 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 5207102f6c9e79c969c0c5f2b46df4a9800ca452..b48a03b4b259043fbca8777421280823012962d1 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 905458fd50bfbc02f23473f11858ca10ffd2f4dc..fdcc80edbb567f981b6e558b6f14052f06f28e95 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 ab5563997d409d40ceec4d0957438bbc89a17996..07a1ab75502a5521658a88d239c91b02d4773abd 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 e6bb3239f4214c26aa1d70d4ca4ac50f63148ad5..c7c1a154fc47df9c137754359a3522ee09dfc41f 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 b8987db214b731653d11c7af8882aae7615af3ee..8367db8b8c58f7ddedc9ddca39e73a83d8fe9e7c 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 98b825a1067a8ecd0656413626ed58028120e611..22aad2e96cc230d2c1cdfc7a44332f77b36b942e 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 14f0fa8e9dc2741ffb41bdbcf57071b02799e447..54190b2f6ce380d5ec8da80c8e8a664a0338b482 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 0b126a3f4ac0e16e4ae19b8507c5c1352842a7e2..09c86aa4c676f93bae92fcad65fdb41f5c636379 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 c8327f677ae34987de1ff21062162e4ed3ed1f5c..9d3f04758e91c66295b47d4a23624bb963ab748b 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 e9d3908b1ab53d69fc833774a82375902b548610..6981692da9e3f5bb93e7ea5e2f3f79be85c5a64c 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 09d210bc6f98bbe998730a53f14d46a277b8d00b..4d1cc86556b4a9899c84ca93863d695c971f199e 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;