Commit 9dbf2b7d authored by Tom Lane's avatar Tom Lane

Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.

Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements.  The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.

In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs.  Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.

Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.

Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn".  There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.

Andres Freund and Tom Lane
parent 77503a76
...@@ -13,7 +13,7 @@ top_builddir = ../../.. ...@@ -13,7 +13,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ 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 \ dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \
foreigncmds.o functioncmds.o \ foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
......
...@@ -1210,15 +1210,17 @@ BeginCopy(bool is_from, ...@@ -1210,15 +1210,17 @@ BeginCopy(bool is_from,
elog(ERROR, "unexpected rewrite result"); elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten); query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
/* Query mustn't use INTO, either */ /* The grammar allows SELECT INTO, but we don't support that */
if (query->intoClause) if (query->utilityStmt != NULL &&
IsA(query->utilityStmt, CreateTableAsStmt))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported"))); errmsg("COPY (SELECT INTO) is not supported")));
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
/* plan the query */ /* plan the query */
plan = planner(query, 0, NULL); plan = planner(query, 0, NULL);
......
This diff is collapsed.
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/createas.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "commands/prepare.h" #include "commands/prepare.h"
#include "executor/hashjoin.h" #include "executor/hashjoin.h"
...@@ -45,7 +46,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; ...@@ -45,7 +46,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
#define X_CLOSE_IMMEDIATE 2 #define X_CLOSE_IMMEDIATE 2
#define X_NOWHITESPACE 4 #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); const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname, static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es); ExplainState *es);
...@@ -212,7 +213,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, ...@@ -212,7 +213,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
/* Explain every plan */ /* Explain every plan */
foreach(l, rewritten) foreach(l, rewritten)
{ {
ExplainOneQuery((Query *) lfirst(l), &es, queryString, params); ExplainOneQuery((Query *) lfirst(l), NULL, &es,
queryString, params);
/* Separate plans with an appropriate separator */ /* Separate plans with an appropriate separator */
if (lnext(l) != NULL) if (lnext(l) != NULL)
...@@ -288,21 +290,23 @@ ExplainResultDesc(ExplainStmt *stmt) ...@@ -288,21 +290,23 @@ ExplainResultDesc(ExplainStmt *stmt)
/* /*
* ExplainOneQuery - * ExplainOneQuery -
* print out the execution plan for one Query * print out the execution plan for one Query
*
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
*/ */
static void static void
ExplainOneQuery(Query *query, ExplainState *es, ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params) const char *queryString, ParamListInfo params)
{ {
/* planner will not cope with utility statements */ /* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY) if (query->commandType == CMD_UTILITY)
{ {
ExplainOneUtility(query->utilityStmt, es, queryString, params); ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
return; return;
} }
/* if an advisor plugin is present, let it manage things */ /* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook) if (ExplainOneQuery_hook)
(*ExplainOneQuery_hook) (query, es, queryString, params); (*ExplainOneQuery_hook) (query, into, es, queryString, params);
else else
{ {
PlannedStmt *plan; PlannedStmt *plan;
...@@ -311,7 +315,7 @@ ExplainOneQuery(Query *query, ExplainState *es, ...@@ -311,7 +315,7 @@ ExplainOneQuery(Query *query, ExplainState *es,
plan = pg_plan_query(query, 0, params); plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */ /* 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, ...@@ -321,18 +325,36 @@ ExplainOneQuery(Query *query, ExplainState *es,
* (In general, utility statements don't have plans, but there are some * (In general, utility statements don't have plans, but there are some
* we treat as special cases) * 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 * This is exported because it's called back from prepare.c in the
* EXPLAIN EXECUTE case * EXPLAIN EXECUTE case.
*/ */
void void
ExplainOneUtility(Node *utilityStmt, ExplainState *es, ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params) const char *queryString, ParamListInfo params)
{ {
if (utilityStmt == NULL) if (utilityStmt == NULL)
return; return;
if (IsA(utilityStmt, ExecuteStmt)) if (IsA(utilityStmt, CreateTableAsStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es, {
/*
* 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); queryString, params);
else if (IsA(utilityStmt, NotifyStmt)) else if (IsA(utilityStmt, NotifyStmt))
{ {
...@@ -356,6 +378,9 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es, ...@@ -356,6 +378,9 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
* given a planned query, execute it if needed, and then print * given a planned query, execute it if needed, and then print
* EXPLAIN output * 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, * 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 * 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 * query. This is different from pre-8.3 behavior but seems more useful than
...@@ -366,9 +391,10 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es, ...@@ -366,9 +391,10 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
* to call it. * to call it.
*/ */
void void
ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params) const char *queryString, ParamListInfo params)
{ {
DestReceiver *dest;
QueryDesc *queryDesc; QueryDesc *queryDesc;
instr_time starttime; instr_time starttime;
double totaltime = 0; double totaltime = 0;
...@@ -392,16 +418,27 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, ...@@ -392,16 +418,27 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
PushCopiedSnapshot(GetActiveSnapshot()); PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId(); 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, queryDesc = CreateQueryDesc(plannedstmt, queryString,
GetActiveSnapshot(), InvalidSnapshot, GetActiveSnapshot(), InvalidSnapshot,
None_Receiver, params, instrument_option); dest, params, instrument_option);
/* Select execution options */ /* Select execution options */
if (es->analyze) if (es->analyze)
eflags = 0; /* default run-to-completion flags */ eflags = 0; /* default run-to-completion flags */
else else
eflags = EXEC_FLAG_EXPLAIN_ONLY; eflags = EXEC_FLAG_EXPLAIN_ONLY;
if (into)
eflags |= GetIntoRelEFlags(into);
/* call ExecutorStart to prepare the plan for execution */ /* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, eflags); ExecutorStart(queryDesc, eflags);
...@@ -409,8 +446,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es, ...@@ -409,8 +446,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
/* Execute the plan for statistics if asked for */ /* Execute the plan for statistics if asked for */
if (es->analyze) 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 */ /* run the plan */
ExecutorRun(queryDesc, ForwardScanDirection, 0L); ExecutorRun(queryDesc, dir, 0L);
/* run cleanup too */ /* run cleanup too */
ExecutorFinish(queryDesc); ExecutorFinish(queryDesc);
......
...@@ -121,7 +121,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params, ...@@ -121,7 +121,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
/* /*
* Start execution, inserting parameters if any. * Start execution, inserting parameters if any.
*/ */
PortalStart(portal, params, true); PortalStart(portal, params, 0, true);
Assert(portal->strategy == PORTAL_ONE_SELECT); Assert(portal->strategy == PORTAL_ONE_SELECT);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/createas.h"
#include "commands/prepare.h" #include "commands/prepare.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
...@@ -170,7 +171,12 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -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 * 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 * two query strings at once. The passed-in queryString is that of the
...@@ -179,8 +185,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) ...@@ -179,8 +185,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* source is that of the original PREPARE. * source is that of the original PREPARE.
*/ */
void void
ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
ParamListInfo params, const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag) DestReceiver *dest, char *completionTag)
{ {
PreparedStatement *entry; PreparedStatement *entry;
...@@ -190,6 +196,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -190,6 +196,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
EState *estate = NULL; EState *estate = NULL;
Portal portal; Portal portal;
char *query_string; char *query_string;
int eflags;
long count;
/* Look it up in the hash table */ /* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true); entry = FetchPreparedStatement(stmt->name, true);
...@@ -222,25 +230,27 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -222,25 +230,27 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal), query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
entry->plansource->query_string); 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 * For CREATE TABLE ... AS EXECUTE, we must verify that the prepared
* so that we can modify its destination (yech, but this has always been * statement is one that produces tuples. Currently we insist that it be
* ugly). For regular EXECUTE we can just use the cached query, since the * a plain old SELECT. In future we might consider supporting other
* executor is read-only. * 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; 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) if (list_length(plan_list) != 1)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
...@@ -252,20 +262,21 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -252,20 +262,21 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT"))); 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 ... */ /* And tell PortalRun whether to run to completion or not */
ReleaseCachedPlan(cplan, true); if (intoClause->skipData)
/* ... and we don't want the portal to depend on it, either */ count = 0;
cplan = NULL; else
count = FETCH_ALL;
} }
else else
{ {
/* Replan if needed, and increment plan refcount for portal */ /* Plain old EXECUTE */
cplan = GetCachedPlan(entry->plansource, paramLI, false); eflags = 0;
plan_list = cplan->stmt_list; count = FETCH_ALL;
} }
PortalDefineQuery(portal, PortalDefineQuery(portal,
...@@ -276,11 +287,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, ...@@ -276,11 +287,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
cplan); 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); PortalDrop(portal, false);
...@@ -615,11 +626,14 @@ DropAllPreparedStatements(void) ...@@ -615,11 +626,14 @@ DropAllPreparedStatements(void)
/* /*
* Implements the 'EXPLAIN EXECUTE' utility statement. * 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, * Note: the passed-in queryString is that of the EXPLAIN EXECUTE,
* not the original PREPARE; we get the latter string from the plancache. * not the original PREPARE; we get the latter string from the plancache.
*/ */
void void
ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params) const char *queryString, ParamListInfo params)
{ {
PreparedStatement *entry; PreparedStatement *entry;
...@@ -665,27 +679,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es, ...@@ -665,27 +679,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p); PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt)) if (IsA(pstmt, PlannedStmt))
{ ExplainOnePlan(pstmt, into, es, query_string, paramLI);
if (execstmt->into)
{
if (pstmt->commandType != CMD_SELECT ||
pstmt->utilityStmt != NULL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
/* Copy the stmt so we can modify it */
pstmt = copyObject(pstmt);
pstmt->intoClause = execstmt->into;
}
ExplainOnePlan(pstmt, es, query_string, paramLI);
}
else else
{ ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
ExplainOneUtility((Node *) pstmt, es, query_string, paramLI);
}
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */ /* No need for CommandCounterIncrement, as ExplainOnePlan did it */
......
...@@ -439,9 +439,17 @@ DefineView(ViewStmt *stmt, const char *queryString) ...@@ -439,9 +439,17 @@ DefineView(ViewStmt *stmt, const char *queryString)
/* /*
* The grammar should ensure that the result is a single SELECT Query. * 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) || if (!IsA(viewParse, Query))
viewParse->commandType != CMD_SELECT) 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"); elog(ERROR, "unexpected parse analysis result");
/* /*
...@@ -449,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString) ...@@ -449,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString)
* DefineQueryRewrite(), but that function will complain about a bogus ON * DefineQueryRewrite(), but that function will complain about a bogus ON
* SELECT rule, and we'd rather the message complain about a view. * 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) if (viewParse->hasModifyingCTE)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
......
This diff is collapsed.
...@@ -137,8 +137,6 @@ CreateExecutorState(void) ...@@ -137,8 +137,6 @@ CreateExecutorState(void)
estate->es_top_eflags = 0; estate->es_top_eflags = 0;
estate->es_instrument = 0; estate->es_instrument = 0;
estate->es_select_into = false;
estate->es_into_oids = false;
estate->es_finished = false; estate->es_finished = false;
estate->es_exprcontexts = NIL; estate->es_exprcontexts = NIL;
......
...@@ -535,7 +535,6 @@ init_execution_state(List *queryTree_list, ...@@ -535,7 +535,6 @@ init_execution_state(List *queryTree_list,
if (ps->commandType == CMD_SELECT && if (ps->commandType == CMD_SELECT &&
ps->utilityStmt == NULL && ps->utilityStmt == NULL &&
ps->intoClause == NULL &&
!ps->hasModifyingCTE) !ps->hasModifyingCTE)
fcache->lazyEval = lasttages->lazyEval = true; fcache->lazyEval = lasttages->lazyEval = true;
} }
...@@ -1493,8 +1492,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -1493,8 +1492,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
*/ */
if (parse && if (parse &&
parse->commandType == CMD_SELECT && parse->commandType == CMD_SELECT &&
parse->utilityStmt == NULL && parse->utilityStmt == NULL)
parse->intoClause == NULL)
{ {
tlist_ptr = &parse->targetList; tlist_ptr = &parse->targetList;
tlist = parse->targetList; tlist = parse->targetList;
......
...@@ -1284,11 +1284,11 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ...@@ -1284,11 +1284,11 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
* Start portal execution. * Start portal execution.
*/ */
if (read_only) if (read_only)
PortalStart(portal, paramLI, true); PortalStart(portal, paramLI, 0, true);
else else
{ {
CommandCounterIncrement(); CommandCounterIncrement();
PortalStart(portal, paramLI, false); PortalStart(portal, paramLI, 0, false);
} }
Assert(portal->strategy != PORTAL_MULTI_QUERY); Assert(portal->strategy != PORTAL_MULTI_QUERY);
...@@ -1907,17 +1907,39 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -1907,17 +1907,39 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
} }
else else
{ {
char completionTag[COMPLETION_TAG_BUFSIZE];
ProcessUtility(stmt, ProcessUtility(stmt,
plansource->query_string, plansource->query_string,
paramLI, paramLI,
false, /* not top level */ false, /* not top level */
dest, dest,
NULL); completionTag);
/* Update "processed" if stmt returned tuples */ /* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable) if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->processed = _SPI_current->tuptable->alloced -
_SPI_current->tuptable->free; _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) ...@@ -2042,9 +2064,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount)
{ {
case CMD_SELECT: case CMD_SELECT:
Assert(queryDesc->plannedstmt->utilityStmt == NULL); Assert(queryDesc->plannedstmt->utilityStmt == NULL);
if (queryDesc->plannedstmt->intoClause) /* select into table? */ if (queryDesc->dest->mydest != DestSPI)
res = SPI_OK_SELINTO;
else if (queryDesc->dest->mydest != DestSPI)
{ {
/* Don't return SPI_OK_SELECT if we're discarding result */ /* Don't return SPI_OK_SELECT if we're discarding result */
res = SPI_OK_UTILITY; res = SPI_OK_UTILITY;
......
...@@ -86,7 +86,6 @@ _copyPlannedStmt(const PlannedStmt *from) ...@@ -86,7 +86,6 @@ _copyPlannedStmt(const PlannedStmt *from)
COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(resultRelations); COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(utilityStmt); COPY_NODE_FIELD(utilityStmt);
COPY_NODE_FIELD(intoClause);
COPY_NODE_FIELD(subplans); COPY_NODE_FIELD(subplans);
COPY_BITMAPSET_FIELD(rewindPlanIDs); COPY_BITMAPSET_FIELD(rewindPlanIDs);
COPY_NODE_FIELD(rowMarks); COPY_NODE_FIELD(rowMarks);
...@@ -2406,7 +2405,6 @@ _copyQuery(const Query *from) ...@@ -2406,7 +2405,6 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(canSetTag); COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(utilityStmt); COPY_NODE_FIELD(utilityStmt);
COPY_SCALAR_FIELD(resultRelation); COPY_SCALAR_FIELD(resultRelation);
COPY_NODE_FIELD(intoClause);
COPY_SCALAR_FIELD(hasAggs); COPY_SCALAR_FIELD(hasAggs);
COPY_SCALAR_FIELD(hasWindowFuncs); COPY_SCALAR_FIELD(hasWindowFuncs);
COPY_SCALAR_FIELD(hasSubLinks); COPY_SCALAR_FIELD(hasSubLinks);
...@@ -3194,6 +3192,18 @@ _copyExplainStmt(const ExplainStmt *from) ...@@ -3194,6 +3192,18 @@ _copyExplainStmt(const ExplainStmt *from)
return newnode; 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 * static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from) _copyCreateSeqStmt(const CreateSeqStmt *from)
{ {
...@@ -3602,7 +3612,6 @@ _copyExecuteStmt(const ExecuteStmt *from) ...@@ -3602,7 +3612,6 @@ _copyExecuteStmt(const ExecuteStmt *from)
ExecuteStmt *newnode = makeNode(ExecuteStmt); ExecuteStmt *newnode = makeNode(ExecuteStmt);
COPY_STRING_FIELD(name); COPY_STRING_FIELD(name);
COPY_NODE_FIELD(into);
COPY_NODE_FIELD(params); COPY_NODE_FIELD(params);
return newnode; return newnode;
...@@ -4234,6 +4243,9 @@ copyObject(const void *from) ...@@ -4234,6 +4243,9 @@ copyObject(const void *from)
case T_ExplainStmt: case T_ExplainStmt:
retval = _copyExplainStmt(from); retval = _copyExplainStmt(from);
break; break;
case T_CreateTableAsStmt:
retval = _copyCreateTableAsStmt(from);
break;
case T_CreateSeqStmt: case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from); retval = _copyCreateSeqStmt(from);
break; break;
......
...@@ -900,7 +900,6 @@ _equalQuery(const Query *a, const Query *b) ...@@ -900,7 +900,6 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(canSetTag); COMPARE_SCALAR_FIELD(canSetTag);
COMPARE_NODE_FIELD(utilityStmt); COMPARE_NODE_FIELD(utilityStmt);
COMPARE_SCALAR_FIELD(resultRelation); COMPARE_SCALAR_FIELD(resultRelation);
COMPARE_NODE_FIELD(intoClause);
COMPARE_SCALAR_FIELD(hasAggs); COMPARE_SCALAR_FIELD(hasAggs);
COMPARE_SCALAR_FIELD(hasWindowFuncs); COMPARE_SCALAR_FIELD(hasWindowFuncs);
COMPARE_SCALAR_FIELD(hasSubLinks); COMPARE_SCALAR_FIELD(hasSubLinks);
...@@ -1564,6 +1563,16 @@ _equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b) ...@@ -1564,6 +1563,16 @@ _equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b)
return true; 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 static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b) _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{ {
...@@ -1908,7 +1917,6 @@ static bool ...@@ -1908,7 +1917,6 @@ static bool
_equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b) _equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b)
{ {
COMPARE_STRING_FIELD(name); COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(into);
COMPARE_NODE_FIELD(params); COMPARE_NODE_FIELD(params);
return true; return true;
...@@ -2793,6 +2801,9 @@ equal(const void *a, const void *b) ...@@ -2793,6 +2801,9 @@ equal(const void *a, const void *b)
case T_ExplainStmt: case T_ExplainStmt:
retval = _equalExplainStmt(a, b); retval = _equalExplainStmt(a, b);
break; break;
case T_CreateTableAsStmt:
retval = _equalCreateTableAsStmt(a, b);
break;
case T_CreateSeqStmt: case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b); retval = _equalCreateSeqStmt(a, b);
break; break;
......
...@@ -250,7 +250,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) ...@@ -250,7 +250,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(resultRelations); WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(utilityStmt); WRITE_NODE_FIELD(utilityStmt);
WRITE_NODE_FIELD(intoClause);
WRITE_NODE_FIELD(subplans); WRITE_NODE_FIELD(subplans);
WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_BITMAPSET_FIELD(rewindPlanIDs);
WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(rowMarks);
...@@ -2181,7 +2180,6 @@ _outQuery(StringInfo str, const Query *node) ...@@ -2181,7 +2180,6 @@ _outQuery(StringInfo str, const Query *node)
appendStringInfo(str, " :utilityStmt <>"); appendStringInfo(str, " :utilityStmt <>");
WRITE_INT_FIELD(resultRelation); WRITE_INT_FIELD(resultRelation);
WRITE_NODE_FIELD(intoClause);
WRITE_BOOL_FIELD(hasAggs); WRITE_BOOL_FIELD(hasAggs);
WRITE_BOOL_FIELD(hasWindowFuncs); WRITE_BOOL_FIELD(hasWindowFuncs);
WRITE_BOOL_FIELD(hasSubLinks); WRITE_BOOL_FIELD(hasSubLinks);
......
...@@ -198,7 +198,6 @@ _readQuery(void) ...@@ -198,7 +198,6 @@ _readQuery(void)
READ_BOOL_FIELD(canSetTag); READ_BOOL_FIELD(canSetTag);
READ_NODE_FIELD(utilityStmt); READ_NODE_FIELD(utilityStmt);
READ_INT_FIELD(resultRelation); READ_INT_FIELD(resultRelation);
READ_NODE_FIELD(intoClause);
READ_BOOL_FIELD(hasAggs); READ_BOOL_FIELD(hasAggs);
READ_BOOL_FIELD(hasWindowFuncs); READ_BOOL_FIELD(hasWindowFuncs);
READ_BOOL_FIELD(hasSubLinks); READ_BOOL_FIELD(hasSubLinks);
......
...@@ -233,7 +233,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) ...@@ -233,7 +233,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->rtable = glob->finalrtable; result->rtable = glob->finalrtable;
result->resultRelations = glob->resultRelations; result->resultRelations = glob->resultRelations;
result->utilityStmt = parse->utilityStmt; result->utilityStmt = parse->utilityStmt;
result->intoClause = parse->intoClause;
result->subplans = glob->subplans; result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs; result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks; result->rowMarks = glob->finalrowmarks;
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "optimizer/pathnode.h" #include "optimizer/pathnode.h"
#include "optimizer/planmain.h" #include "optimizer/planmain.h"
#include "optimizer/tlist.h" #include "optimizer/tlist.h"
#include "tcop/utility.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -1887,16 +1888,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context) ...@@ -1887,16 +1888,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
Query *query = (Query *) node; Query *query = (Query *) node;
ListCell *lc; ListCell *lc;
if (query->commandType == CMD_UTILITY) while (query->commandType == CMD_UTILITY)
{ {
/* Ignore utility statements, except EXPLAIN */ /*
if (IsA(query->utilityStmt, ExplainStmt)) * Ignore utility statements, except those (such as EXPLAIN) that
{ * contain a parsed-but-not-planned query.
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query; */
Assert(IsA(query, Query)); query = UtilityContainsQuery(query->utilityStmt);
Assert(query->commandType != CMD_UTILITY); if (query == NULL)
}
else
return false; return false;
} }
......
...@@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query) ...@@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query)
* are complex. * are complex.
*/ */
if (query->commandType != CMD_SELECT || if (query->commandType != CMD_SELECT ||
query->intoClause ||
query->setOperations || query->setOperations ||
query->hasAggs || query->hasAggs ||
query->hasWindowFuncs || query->hasWindowFuncs ||
......
...@@ -1136,8 +1136,7 @@ is_simple_subquery(Query *subquery) ...@@ -1136,8 +1136,7 @@ is_simple_subquery(Query *subquery)
*/ */
if (!IsA(subquery, Query) || if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT || subquery->commandType != CMD_SELECT ||
subquery->utilityStmt != NULL || subquery->utilityStmt != NULL)
subquery->intoClause != NULL)
elog(ERROR, "subquery is bogus"); elog(ERROR, "subquery is bogus");
/* /*
...@@ -1223,8 +1222,7 @@ is_simple_union_all(Query *subquery) ...@@ -1223,8 +1222,7 @@ is_simple_union_all(Query *subquery)
/* Let's just make sure it's a valid subselect ... */ /* Let's just make sure it's a valid subselect ... */
if (!IsA(subquery, Query) || if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT || subquery->commandType != CMD_SELECT ||
subquery->utilityStmt != NULL || subquery->utilityStmt != NULL)
subquery->intoClause != NULL)
elog(ERROR, "subquery is bogus"); elog(ERROR, "subquery is bogus");
/* Is it a set-operation query at all? */ /* Is it a set-operation query at all? */
......
...@@ -4158,7 +4158,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, ...@@ -4158,7 +4158,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
pstate->p_sourcetext = src; pstate->p_sourcetext = src;
sql_fn_parser_setup(pstate, pinfo); sql_fn_parser_setup(pstate, pinfo);
querytree = transformStmt(pstate, linitial(raw_parsetree_list)); querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate); free_parsestate(pstate);
...@@ -4168,7 +4168,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, ...@@ -4168,7 +4168,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
if (!IsA(querytree, Query) || if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT || querytree->commandType != CMD_SELECT ||
querytree->utilityStmt || querytree->utilityStmt ||
querytree->intoClause ||
querytree->hasAggs || querytree->hasAggs ||
querytree->hasWindowFuncs || querytree->hasWindowFuncs ||
querytree->hasSubLinks || querytree->hasSubLinks ||
...@@ -4678,12 +4677,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) ...@@ -4678,12 +4677,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
querytree = linitial(querytree_list); 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) || if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT || querytree->commandType != CMD_SELECT ||
querytree->utilityStmt || querytree->utilityStmt)
querytree->intoClause)
goto fail; goto fail;
/* /*
......
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
* utility commands, no locks are obtained here (and if they were, we could * 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 * 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. * for utility commands is to just dump them into a Query node untransformed.
* DECLARE CURSOR and EXPLAIN are exceptions because they contain * DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they
* optimizable statements. * contain optimizable statements, which we should transform.
* *
* *
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
...@@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate, ...@@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt); DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate, static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt); ExplainStmt *stmt);
static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry, static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown); LockingClause *lc, bool pushedDown);
...@@ -91,7 +93,7 @@ parse_analyze(Node *parseTree, const char *sourceText, ...@@ -91,7 +93,7 @@ parse_analyze(Node *parseTree, const char *sourceText,
if (numParams > 0) if (numParams > 0)
parse_fixed_parameters(pstate, paramTypes, numParams); parse_fixed_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree); query = transformTopLevelStmt(pstate, parseTree);
free_parsestate(pstate); free_parsestate(pstate);
...@@ -118,7 +120,7 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText, ...@@ -118,7 +120,7 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
parse_variable_parameters(pstate, paramTypes, numParams); parse_variable_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree); query = transformTopLevelStmt(pstate, parseTree);
/* make sure all is well with parameter types */ /* make sure all is well with parameter types */
check_variable_parameters(pstate, query); check_variable_parameters(pstate, query);
...@@ -151,8 +153,52 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState, ...@@ -151,8 +153,52 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
} }
/* /*
* transformStmt - * transformTopLevelStmt -
* transform a Parse tree into a Query tree. * 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 * Query *
transformStmt(ParseState *pstate, Node *parseTree) transformStmt(ParseState *pstate, Node *parseTree)
...@@ -202,6 +248,11 @@ transformStmt(ParseState *pstate, Node *parseTree) ...@@ -202,6 +248,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(ExplainStmt *) parseTree); (ExplainStmt *) parseTree);
break; break;
case T_CreateTableAsStmt:
result = transformCreateTableAsStmt(pstate,
(CreateTableAsStmt *) parseTree);
break;
default: default:
/* /*
...@@ -258,6 +309,7 @@ analyze_requires_snapshot(Node *parseTree) ...@@ -258,6 +309,7 @@ analyze_requires_snapshot(Node *parseTree)
break; break;
case T_ExplainStmt: case T_ExplainStmt:
case T_CreateTableAsStmt:
/* yes, because we must analyze the contained statement */ /* yes, because we must analyze the contained statement */
result = true; result = true;
break; break;
...@@ -459,17 +511,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) ...@@ -459,17 +511,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
free_parsestate(sub_pstate); 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) || if (!IsA(selectQuery, Query) ||
selectQuery->commandType != CMD_SELECT || selectQuery->commandType != CMD_SELECT ||
selectQuery->utilityStmt != NULL) selectQuery->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT"); 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 * Make the source be a subquery in the INSERT's rangetable, and add
...@@ -539,6 +585,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) ...@@ -539,6 +585,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
int sublist_length = -1; int sublist_length = -1;
int i; int i;
Assert(selectStmt->intoClause == NULL);
foreach(lc, selectStmt->valuesLists) foreach(lc, selectStmt->valuesLists)
{ {
List *sublist = (List *) lfirst(lc); List *sublist = (List *) lfirst(lc);
...@@ -653,6 +701,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) ...@@ -653,6 +701,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *valuesLists = selectStmt->valuesLists; List *valuesLists = selectStmt->valuesLists;
Assert(list_length(valuesLists) == 1); Assert(list_length(valuesLists) == 1);
Assert(selectStmt->intoClause == NULL);
/* Do basic expression transformation (same as a ROW() expr) */ /* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate, exprList = transformExpressionList(pstate,
...@@ -886,6 +935,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -886,6 +935,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasModifyingCTE = pstate->p_hasModifyingCTE; 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 */ /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause; pstate->p_locking_clause = stmt->lockingClause;
...@@ -963,9 +1020,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -963,9 +1020,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
pstate->p_windowdefs, pstate->p_windowdefs,
&qry->targetList); &qry->targetList);
/* SELECT INTO/CREATE TABLE AS spec is just passed through */
qry->intoClause = stmt->intoClause;
qry->rtable = pstate->p_rtable; qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual); qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
...@@ -1013,6 +1067,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) ...@@ -1013,6 +1067,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
/* Most SELECT stuff doesn't apply in a VALUES clause */ /* Most SELECT stuff doesn't apply in a VALUES clause */
Assert(stmt->distinctClause == NIL); Assert(stmt->distinctClause == NIL);
Assert(stmt->intoClause == NULL);
Assert(stmt->targetList == NIL); Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL); Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL); Assert(stmt->whereClause == NULL);
...@@ -1185,9 +1240,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) ...@@ -1185,9 +1240,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"))); 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 * There mustn't have been any table references in the expressions, else
* strange things would happen, like Cartesian products of those tables * strange things would happen, like Cartesian products of those tables
...@@ -1286,21 +1338,27 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) ...@@ -1286,21 +1338,27 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
} }
/* /*
* Find leftmost leaf SelectStmt; extract the one-time-only items from it * Find leftmost leaf SelectStmt. We currently only need to do this in
* and from the top-level node. * 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; leftmostSelect = stmt->larg;
while (leftmostSelect && leftmostSelect->op != SETOP_NONE) while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
leftmostSelect = leftmostSelect->larg; leftmostSelect = leftmostSelect->larg;
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) && Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
leftmostSelect->larg == NULL); leftmostSelect->larg == NULL);
qry->intoClause = leftmostSelect->intoClause; if (leftmostSelect->intoClause)
ereport(ERROR,
/* clear this to prevent complaints in transformSetOperationTree() */ (errcode(ERRCODE_SYNTAX_ERROR),
leftmostSelect->intoClause = NULL; 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 * not let transformSetOperationTree() see them --- else it'll just
* recurse right back here! * recurse right back here!
*/ */
...@@ -2107,14 +2165,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) ...@@ -2107,14 +2165,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
result->utilityStmt != NULL) result->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR"); 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 * We also disallow data-modifying WITH in a cursor. (This could be
* allowed, but the semantics of when the updates occur might be * allowed, but the semantics of when the updates occur might be
...@@ -2170,6 +2220,29 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) ...@@ -2170,6 +2220,29 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
{ {
Query *result; 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 */ /* transform contained query */
stmt->query = (Node *) transformStmt(pstate, stmt->query); stmt->query = (Node *) transformStmt(pstate, stmt->query);
......
...@@ -124,7 +124,6 @@ static void check_qualified_name(List *names, core_yyscan_t yyscanner); ...@@ -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_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner); static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
static List *extractArgTypes(List *parameters); static List *extractArgTypes(List *parameters);
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt, static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause, List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount, Node *limitOffset, Node *limitCount,
...@@ -3044,31 +3043,27 @@ ExistingIndex: USING INDEX index_name { $$ = $3; } ...@@ -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: CreateAsStmt:
CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data
{ {
/* CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
* When the SelectStmt is a set-operation tree, we must ctas->query = $6;
* stuff the INTO information into the leftmost component ctas->into = $4;
* Select, because that's where analyze.c will expect ctas->is_select_into = false;
* 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;
/* cram additional flags into the IntoClause */ /* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2; $4->rel->relpersistence = $2;
$4->skipData = !($7); $4->skipData = !($7);
$$ = $6; $$ = (Node *) ctas;
} }
; ;
...@@ -8285,20 +8280,22 @@ ExecuteStmt: EXECUTE name execute_param_clause ...@@ -8285,20 +8280,22 @@ ExecuteStmt: EXECUTE name execute_param_clause
ExecuteStmt *n = makeNode(ExecuteStmt); ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $2; n->name = $2;
n->params = $3; n->params = $3;
n->into = NULL;
$$ = (Node *) n; $$ = (Node *) n;
} }
| CREATE OptTemp TABLE create_as_target AS | CREATE OptTemp TABLE create_as_target AS
EXECUTE name execute_param_clause opt_with_data EXECUTE name execute_param_clause opt_with_data
{ {
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ExecuteStmt *n = makeNode(ExecuteStmt); ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $7; n->name = $7;
n->params = $8; n->params = $8;
n->into = $4; ctas->query = (Node *) n;
ctas->into = $4;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */ /* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2; $4->rel->relpersistence = $2;
$4->skipData = !($9); $4->skipData = !($9);
$$ = (Node *) n; $$ = (Node *) ctas;
} }
; ;
...@@ -12870,18 +12867,6 @@ extractArgTypes(List *parameters) ...@@ -12870,18 +12867,6 @@ extractArgTypes(List *parameters)
return result; 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() /* insertSelectOptions()
* Insert ORDER BY, etc into an already-constructed SelectStmt. * Insert ORDER BY, etc into an already-constructed SelectStmt.
* *
......
...@@ -495,12 +495,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) ...@@ -495,12 +495,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
query->commandType != CMD_SELECT || query->commandType != CMD_SELECT ||
query->utilityStmt != NULL) query->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in subquery in FROM"); 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 * The subquery cannot make use of any variables from FROM items created
......
...@@ -253,13 +253,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) ...@@ -253,13 +253,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
if (query->utilityStmt != NULL) if (query->utilityStmt != NULL)
elog(ERROR, "unexpected utility statement in WITH"); 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, * 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. * because it's not clear when such a modification should be executed.
......
...@@ -1408,12 +1408,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink) ...@@ -1408,12 +1408,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
qtree->commandType != CMD_SELECT || qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL) qtree->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in SubLink"); 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; sublink->subselect = (Node *) qtree;
......
...@@ -323,8 +323,7 @@ DefineQueryRewrite(char *rulename, ...@@ -323,8 +323,7 @@ DefineQueryRewrite(char *rulename,
query = (Query *) linitial(action); query = (Query *) linitial(action);
if (!is_instead || if (!is_instead ||
query->commandType != CMD_SELECT || query->commandType != CMD_SELECT ||
query->utilityStmt != NULL || query->utilityStmt != NULL)
query->intoClause != NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rules on SELECT must have action INSTEAD SELECT"))); errmsg("rules on SELECT must have action INSTEAD SELECT")));
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
#include "access/printtup.h" #include "access/printtup.h"
#include "access/xact.h" #include "access/xact.h"
#include "commands/copy.h" #include "commands/copy.h"
#include "executor/executor.h" #include "commands/createas.h"
#include "executor/functions.h" #include "executor/functions.h"
#include "executor/tstoreReceiver.h" #include "executor/tstoreReceiver.h"
#include "libpq/libpq.h" #include "libpq/libpq.h"
...@@ -118,7 +118,7 @@ CreateDestReceiver(CommandDest dest) ...@@ -118,7 +118,7 @@ CreateDestReceiver(CommandDest dest)
return CreateTuplestoreDestReceiver(); return CreateTuplestoreDestReceiver();
case DestIntoRel: case DestIntoRel:
return CreateIntoRelDestReceiver(); return CreateIntoRelDestReceiver(NULL);
case DestCopyOut: case DestCopyOut:
return CreateCopyDestReceiver(); return CreateCopyDestReceiver();
......
...@@ -624,7 +624,7 @@ pg_analyze_and_rewrite_params(Node *parsetree, ...@@ -624,7 +624,7 @@ pg_analyze_and_rewrite_params(Node *parsetree,
pstate->p_sourcetext = query_string; pstate->p_sourcetext = query_string;
(*parserSetup) (pstate, parserSetupArg); (*parserSetup) (pstate, parserSetupArg);
query = transformStmt(pstate, parsetree); query = transformTopLevelStmt(pstate, parsetree);
free_parsestate(pstate); free_parsestate(pstate);
...@@ -975,7 +975,7 @@ exec_simple_query(const char *query_string) ...@@ -975,7 +975,7 @@ exec_simple_query(const char *query_string)
* end up being able to do this, keeping the parse/plan snapshot around * end up being able to do this, keeping the parse/plan snapshot around
* until after we start the portal doesn't cost much. * 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 */ /* Done with the snapshot used for parsing/planning */
if (snapshot_set) if (snapshot_set)
...@@ -1709,7 +1709,7 @@ exec_bind_message(StringInfo input_message) ...@@ -1709,7 +1709,7 @@ exec_bind_message(StringInfo input_message)
* for query execution (currently, reuse will only occur if * for query execution (currently, reuse will only occur if
* PORTAL_ONE_SELECT mode is chosen). * 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 */ /* Done with the snapshot used for parameter I/O and parsing/planning */
if (snapshot_set) if (snapshot_set)
......
...@@ -260,8 +260,7 @@ ChoosePortalStrategy(List *stmts) ...@@ -260,8 +260,7 @@ ChoosePortalStrategy(List *stmts)
if (query->canSetTag) if (query->canSetTag)
{ {
if (query->commandType == CMD_SELECT && if (query->commandType == CMD_SELECT &&
query->utilityStmt == NULL && query->utilityStmt == NULL)
query->intoClause == NULL)
{ {
if (query->hasModifyingCTE) if (query->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH; return PORTAL_ONE_MOD_WITH;
...@@ -285,8 +284,7 @@ ChoosePortalStrategy(List *stmts) ...@@ -285,8 +284,7 @@ ChoosePortalStrategy(List *stmts)
if (pstmt->canSetTag) if (pstmt->canSetTag)
{ {
if (pstmt->commandType == CMD_SELECT && if (pstmt->commandType == CMD_SELECT &&
pstmt->utilityStmt == NULL && pstmt->utilityStmt == NULL)
pstmt->intoClause == NULL)
{ {
if (pstmt->hasModifyingCTE) if (pstmt->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH; return PORTAL_ONE_MOD_WITH;
...@@ -395,8 +393,7 @@ FetchStatementTargetList(Node *stmt) ...@@ -395,8 +393,7 @@ FetchStatementTargetList(Node *stmt)
else else
{ {
if (query->commandType == CMD_SELECT && if (query->commandType == CMD_SELECT &&
query->utilityStmt == NULL && query->utilityStmt == NULL)
query->intoClause == NULL)
return query->targetList; return query->targetList;
if (query->returningList) if (query->returningList)
return query->returningList; return query->returningList;
...@@ -408,8 +405,7 @@ FetchStatementTargetList(Node *stmt) ...@@ -408,8 +405,7 @@ FetchStatementTargetList(Node *stmt)
PlannedStmt *pstmt = (PlannedStmt *) stmt; PlannedStmt *pstmt = (PlannedStmt *) stmt;
if (pstmt->commandType == CMD_SELECT && if (pstmt->commandType == CMD_SELECT &&
pstmt->utilityStmt == NULL && pstmt->utilityStmt == NULL)
pstmt->intoClause == NULL)
return pstmt->planTree->targetlist; return pstmt->planTree->targetlist;
if (pstmt->hasReturning) if (pstmt->hasReturning)
return pstmt->planTree->targetlist; return pstmt->planTree->targetlist;
...@@ -430,7 +426,6 @@ FetchStatementTargetList(Node *stmt) ...@@ -430,7 +426,6 @@ FetchStatementTargetList(Node *stmt)
ExecuteStmt *estmt = (ExecuteStmt *) stmt; ExecuteStmt *estmt = (ExecuteStmt *) stmt;
PreparedStatement *entry; PreparedStatement *entry;
Assert(!estmt->into);
entry = FetchPreparedStatement(estmt->name, true); entry = FetchPreparedStatement(estmt->name, true);
return FetchPreparedStatementTargetList(entry); return FetchPreparedStatementTargetList(entry);
} }
...@@ -442,9 +437,15 @@ FetchStatementTargetList(Node *stmt) ...@@ -442,9 +437,15 @@ FetchStatementTargetList(Node *stmt)
* Prepare a portal for execution. * Prepare a portal for execution.
* *
* Caller must already have created the portal, done PortalDefineQuery(), * Caller must already have created the portal, done PortalDefineQuery(),
* and adjusted portal options if needed. If parameters are needed by * and adjusted portal options if needed.
* the query, they must be passed in here (caller is responsible for *
* giving them appropriate lifetime). * 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 * The use_active_snapshot parameter is currently used only for
* PORTAL_ONE_SELECT portals. If it is true, the active snapshot will * PORTAL_ONE_SELECT portals. If it is true, the active snapshot will
...@@ -456,14 +457,15 @@ FetchStatementTargetList(Node *stmt) ...@@ -456,14 +457,15 @@ FetchStatementTargetList(Node *stmt)
* tupdesc (if any) is known. * tupdesc (if any) is known.
*/ */
void void
PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot) PortalStart(Portal portal, ParamListInfo params,
int eflags, bool use_active_snapshot)
{ {
Portal saveActivePortal; Portal saveActivePortal;
ResourceOwner saveResourceOwner; ResourceOwner saveResourceOwner;
MemoryContext savePortalContext; MemoryContext savePortalContext;
MemoryContext oldContext; MemoryContext oldContext;
QueryDesc *queryDesc; QueryDesc *queryDesc;
int eflags; int myeflags;
AssertArg(PortalIsValid(portal)); AssertArg(PortalIsValid(portal));
AssertState(portal->status == PORTAL_DEFINED); AssertState(portal->status == PORTAL_DEFINED);
...@@ -517,17 +519,18 @@ PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot) ...@@ -517,17 +519,18 @@ PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot)
/* /*
* If it's a scrollable cursor, executor needs to support * 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) if (portal->cursorOptions & CURSOR_OPT_SCROLL)
eflags = EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD; myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
else else
eflags = 0; /* default run-to-completion flags */ myeflags = eflags;
/* /*
* Call ExecutorStart to prepare the plan for execution * Call ExecutorStart to prepare the plan for execution
*/ */
ExecutorStart(queryDesc, eflags); ExecutorStart(queryDesc, myeflags);
/* /*
* This tells PortalCleanup to shut down the executor * This tells PortalCleanup to shut down the executor
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "commands/collationcmds.h" #include "commands/collationcmds.h"
#include "commands/conversioncmds.h" #include "commands/conversioncmds.h"
#include "commands/copy.h" #include "commands/copy.h"
#include "commands/createas.h"
#include "commands/dbcommands.h" #include "commands/dbcommands.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "commands/discard.h" #include "commands/discard.h"
...@@ -127,9 +128,7 @@ CommandIsReadOnly(Node *parsetree) ...@@ -127,9 +128,7 @@ CommandIsReadOnly(Node *parsetree)
switch (stmt->commandType) switch (stmt->commandType)
{ {
case CMD_SELECT: case CMD_SELECT:
if (stmt->intoClause != NULL) if (stmt->rowMarks != NIL)
return false; /* SELECT INTO */
else if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */ return false; /* SELECT FOR UPDATE/SHARE */
else if (stmt->hasModifyingCTE) else if (stmt->hasModifyingCTE)
return false; /* data-modifying CTE */ return false; /* data-modifying CTE */
...@@ -198,6 +197,7 @@ check_xact_readonly(Node *parsetree) ...@@ -198,6 +197,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateSchemaStmt: case T_CreateSchemaStmt:
case T_CreateSeqStmt: case T_CreateSeqStmt:
case T_CreateStmt: case T_CreateStmt:
case T_CreateTableAsStmt:
case T_CreateTableSpaceStmt: case T_CreateTableSpaceStmt:
case T_CreateTrigStmt: case T_CreateTrigStmt:
case T_CompositeTypeStmt: case T_CompositeTypeStmt:
...@@ -673,7 +673,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -673,7 +673,8 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_ExecuteStmt: case T_ExecuteStmt:
ExecuteQuery((ExecuteStmt *) parsetree, queryString, params, ExecuteQuery((ExecuteStmt *) parsetree, NULL,
queryString, params,
dest, completionTag); dest, completionTag);
break; break;
...@@ -1036,6 +1037,11 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1036,6 +1037,11 @@ standard_ProcessUtility(Node *parsetree,
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest); ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break; break;
case T_CreateTableAsStmt:
ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, completionTag);
break;
case T_VariableSetStmt: case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree); ExecSetVariableStmt((VariableSetStmt *) parsetree);
break; break;
...@@ -1230,8 +1236,6 @@ UtilityReturnsTuples(Node *parsetree) ...@@ -1230,8 +1236,6 @@ UtilityReturnsTuples(Node *parsetree)
ExecuteStmt *stmt = (ExecuteStmt *) parsetree; ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry; PreparedStatement *entry;
if (stmt->into)
return false;
entry = FetchPreparedStatement(stmt->name, false); entry = FetchPreparedStatement(stmt->name, false);
if (!entry) if (!entry)
return false; /* not our business to raise error */ return false; /* not our business to raise error */
...@@ -1282,8 +1286,6 @@ UtilityTupleDescriptor(Node *parsetree) ...@@ -1282,8 +1286,6 @@ UtilityTupleDescriptor(Node *parsetree)
ExecuteStmt *stmt = (ExecuteStmt *) parsetree; ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry; PreparedStatement *entry;
if (stmt->into)
return NULL;
entry = FetchPreparedStatement(stmt->name, false); entry = FetchPreparedStatement(stmt->name, false);
if (!entry) if (!entry)
return NULL; /* not our business to raise error */ return NULL; /* not our business to raise error */
...@@ -1317,9 +1319,8 @@ QueryReturnsTuples(Query *parsetree) ...@@ -1317,9 +1319,8 @@ QueryReturnsTuples(Query *parsetree)
switch (parsetree->commandType) switch (parsetree->commandType)
{ {
case CMD_SELECT: case CMD_SELECT:
/* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */ /* returns tuples ... unless it's DECLARE CURSOR */
if (parsetree->utilityStmt == NULL && if (parsetree->utilityStmt == NULL)
parsetree->intoClause == NULL)
return true; return true;
break; break;
case CMD_INSERT: case CMD_INSERT:
...@@ -1341,6 +1342,37 @@ QueryReturnsTuples(Query *parsetree) ...@@ -1341,6 +1342,37 @@ QueryReturnsTuples(Query *parsetree)
#endif #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 * AlterObjectTypeCommandTag
* helper function for CreateCommandTag * helper function for CreateCommandTag
...@@ -1907,6 +1939,13 @@ CreateCommandTag(Node *parsetree) ...@@ -1907,6 +1939,13 @@ CreateCommandTag(Node *parsetree)
tag = "EXPLAIN"; tag = "EXPLAIN";
break; break;
case T_CreateTableAsStmt:
if (((CreateTableAsStmt *) parsetree)->is_select_into)
tag = "SELECT INTO";
else
tag = "CREATE TABLE AS";
break;
case T_VariableSetStmt: case T_VariableSetStmt:
switch (((VariableSetStmt *) parsetree)->kind) switch (((VariableSetStmt *) parsetree)->kind)
{ {
...@@ -2060,8 +2099,6 @@ CreateCommandTag(Node *parsetree) ...@@ -2060,8 +2099,6 @@ CreateCommandTag(Node *parsetree)
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR"; tag = "DECLARE CURSOR";
} }
else if (stmt->intoClause != NULL)
tag = "SELECT INTO";
else if (stmt->rowMarks != NIL) else if (stmt->rowMarks != NIL)
{ {
/* not 100% but probably close enough */ /* not 100% but probably close enough */
...@@ -2110,8 +2147,6 @@ CreateCommandTag(Node *parsetree) ...@@ -2110,8 +2147,6 @@ CreateCommandTag(Node *parsetree)
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR"; tag = "DECLARE CURSOR";
} }
else if (stmt->intoClause != NULL)
tag = "SELECT INTO";
else if (stmt->rowMarks != NIL) else if (stmt->rowMarks != NIL)
{ {
/* not 100% but probably close enough */ /* not 100% but probably close enough */
...@@ -2179,7 +2214,7 @@ GetCommandLogLevel(Node *parsetree) ...@@ -2179,7 +2214,7 @@ GetCommandLogLevel(Node *parsetree)
case T_SelectStmt: case T_SelectStmt:
if (((SelectStmt *) parsetree)->intoClause) if (((SelectStmt *) parsetree)->intoClause)
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ lev = LOGSTMT_DDL; /* SELECT INTO */
else else
lev = LOGSTMT_ALL; lev = LOGSTMT_ALL;
break; break;
...@@ -2429,6 +2464,10 @@ GetCommandLogLevel(Node *parsetree) ...@@ -2429,6 +2464,10 @@ GetCommandLogLevel(Node *parsetree)
} }
break; break;
case T_CreateTableAsStmt:
lev = LOGSTMT_DDL;
break;
case T_VariableSetStmt: case T_VariableSetStmt:
lev = LOGSTMT_ALL; lev = LOGSTMT_ALL;
break; break;
...@@ -2529,10 +2568,7 @@ GetCommandLogLevel(Node *parsetree) ...@@ -2529,10 +2568,7 @@ GetCommandLogLevel(Node *parsetree)
switch (stmt->commandType) switch (stmt->commandType)
{ {
case CMD_SELECT: case CMD_SELECT:
if (stmt->intoClause != NULL) lev = LOGSTMT_ALL;
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
else
lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
break; break;
case CMD_UPDATE: case CMD_UPDATE:
...@@ -2558,10 +2594,7 @@ GetCommandLogLevel(Node *parsetree) ...@@ -2558,10 +2594,7 @@ GetCommandLogLevel(Node *parsetree)
switch (stmt->commandType) switch (stmt->commandType)
{ {
case CMD_SELECT: case CMD_SELECT:
if (stmt->intoClause != NULL) lev = LOGSTMT_ALL;
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
else
lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
break; break;
case CMD_UPDATE: case CMD_UPDATE:
......
...@@ -1232,20 +1232,16 @@ AcquireExecutorLocks(List *stmt_list, bool acquire) ...@@ -1232,20 +1232,16 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
if (!IsA(plannedstmt, PlannedStmt)) if (!IsA(plannedstmt, PlannedStmt))
{ {
/* /*
* Ignore utility statements, except EXPLAIN which contains a * Ignore utility statements, except those (such as EXPLAIN) that
* parsed-but-not-planned query. Note: it's okay to use * contain a parsed-but-not-planned query. Note: it's okay to use
* ScanQueryForLocks, even though the query hasn't been through * ScanQueryForLocks, even though the query hasn't been through
* rule rewriting, because rewriting doesn't change the query * rule rewriting, because rewriting doesn't change the query
* representation. * representation.
*/ */
if (IsA(plannedstmt, ExplainStmt)) Query *query = UtilityContainsQuery((Node *) plannedstmt);
{
Query *query;
query = (Query *) ((ExplainStmt *) plannedstmt)->query; if (query)
Assert(IsA(query, Query));
ScanQueryForLocks(query, acquire); ScanQueryForLocks(query, acquire);
}
continue; continue;
} }
...@@ -1304,13 +1300,10 @@ AcquirePlannerLocks(List *stmt_list, bool acquire) ...@@ -1304,13 +1300,10 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
if (query->commandType == CMD_UTILITY) if (query->commandType == CMD_UTILITY)
{ {
/* Ignore utility statements, except EXPLAIN */ /* Ignore utility statements, unless they contain a Query */
if (IsA(query->utilityStmt, ExplainStmt)) query = UtilityContainsQuery(query->utilityStmt);
{ if (query)
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
Assert(IsA(query, Query));
ScanQueryForLocks(query, acquire); ScanQueryForLocks(query, acquire);
}
continue; continue;
} }
...@@ -1648,9 +1641,9 @@ ResetPlanCache(void) ...@@ -1648,9 +1641,9 @@ ResetPlanCache(void)
* aborted transactions when we can't revalidate them (cf bug #5269). * aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements * In general there is no point in invalidating utility statements
* since they have no plans anyway. So invalidate it only if it * since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement. (EXPLAIN counts as a * contains at least one non-utility statement, or contains a utility
* non-utility statement, though, since it contains an analyzed query * statement that contains a pre-analyzed query (which could have
* that might have dependencies.) * dependencies.)
*/ */
foreach(lc, plansource->query_list) foreach(lc, plansource->query_list)
{ {
...@@ -1658,12 +1651,13 @@ ResetPlanCache(void) ...@@ -1658,12 +1651,13 @@ ResetPlanCache(void)
Assert(IsA(query, Query)); Assert(IsA(query, Query));
if (query->commandType != CMD_UTILITY || if (query->commandType != CMD_UTILITY ||
IsA(query->utilityStmt, ExplainStmt)) UtilityContainsQuery(query->utilityStmt))
{ {
/* non-utility statement, so invalidate */ /* non-utility statement, so invalidate */
plansource->is_valid = false; plansource->is_valid = false;
if (plansource->gplan) if (plansource->gplan)
plansource->gplan->is_valid = false; plansource->gplan->is_valid = false;
/* no need to look further */
break; break;
} }
} }
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201203111 #define CATALOG_VERSION_NO 201203191
#endif #endif
/*-------------------------------------------------------------------------
*
* 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 */
...@@ -42,6 +42,7 @@ typedef struct ExplainState ...@@ -42,6 +42,7 @@ typedef struct ExplainState
/* Hook for plugins to get control in ExplainOneQuery() */ /* Hook for plugins to get control in ExplainOneQuery() */
typedef void (*ExplainOneQuery_hook_type) (Query *query, typedef void (*ExplainOneQuery_hook_type) (Query *query,
IntoClause *into,
ExplainState *es, ExplainState *es,
const char *queryString, const char *queryString,
ParamListInfo params); ParamListInfo params);
...@@ -59,10 +60,12 @@ extern void ExplainInitState(ExplainState *es); ...@@ -59,10 +60,12 @@ extern void ExplainInitState(ExplainState *es);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt); 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); 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); const char *queryString, ParamListInfo params);
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc); extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);
......
...@@ -35,11 +35,12 @@ typedef struct ...@@ -35,11 +35,12 @@ typedef struct
/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */ /* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
extern void PrepareQuery(PrepareStmt *stmt, const char *queryString); extern void PrepareQuery(PrepareStmt *stmt, const char *queryString);
extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString, extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
ParamListInfo params, const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag); DestReceiver *dest, char *completionTag);
extern void DeallocateQuery(DeallocateStmt *stmt); 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); const char *queryString, ParamListInfo params);
/* Low-level access to stored prepared statements */ /* Low-level access to stored prepared statements */
...@@ -52,6 +53,6 @@ extern void DropPreparedStatement(const char *stmt_name, bool showError); ...@@ -52,6 +53,6 @@ extern void DropPreparedStatement(const char *stmt_name, bool showError);
extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt); extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt); extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
void DropAllPreparedStatements(void); extern void DropAllPreparedStatements(void);
#endif /* PREPARE_H */ #endif /* PREPARE_H */
...@@ -30,8 +30,8 @@ ...@@ -30,8 +30,8 @@
* *
* EXPLAIN_ONLY indicates that the plan tree is being initialized just so * 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 * 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). * of startup should occur. However, error checks (such as permission checks)
* However, error checks (such as permission checks) should be performed. * should be performed.
* *
* REWIND indicates that the plan node should try to efficiently support * REWIND indicates that the plan node should try to efficiently support
* rescans without parameter changes. (Nodes must support ExecReScan calls * rescans without parameter changes. (Nodes must support ExecReScan calls
...@@ -49,12 +49,18 @@ ...@@ -49,12 +49,18 @@
* AfterTriggerBeginQuery/AfterTriggerEndQuery. This does not necessarily * AfterTriggerBeginQuery/AfterTriggerEndQuery. This does not necessarily
* mean that the plan can't queue any AFTER triggers; just that the caller * 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. * 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_EXPLAIN_ONLY 0x0001 /* EXPLAIN, no ANALYZE */
#define EXEC_FLAG_REWIND 0x0002 /* need efficient rescan */ #define EXEC_FLAG_REWIND 0x0002 /* need efficient rescan */
#define EXEC_FLAG_BACKWARD 0x0004 /* need backward scan */ #define EXEC_FLAG_BACKWARD 0x0004 /* need backward scan */
#define EXEC_FLAG_MARK 0x0008 /* need mark/restore */ #define EXEC_FLAG_MARK 0x0008 /* need mark/restore */
#define EXEC_FLAG_SKIP_TRIGGERS 0x0010 /* skip AfterTrigger calls */ #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); ...@@ -204,7 +210,6 @@ extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
extern TupleTableSlot *EvalPlanQualNext(EPQState *epqstate); extern TupleTableSlot *EvalPlanQualNext(EPQState *epqstate);
extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate); extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate);
extern void EvalPlanQualEnd(EPQState *epqstate); extern void EvalPlanQualEnd(EPQState *epqstate);
extern DestReceiver *CreateIntoRelDestReceiver(void);
/* /*
* prototypes from functions in execProcnode.c * prototypes from functions in execProcnode.c
......
...@@ -371,8 +371,6 @@ typedef struct EState ...@@ -371,8 +371,6 @@ typedef struct EState
int es_top_eflags; /* eflags passed to ExecutorStart */ int es_top_eflags; /* eflags passed to ExecutorStart */
int es_instrument; /* OR of InstrumentOption flags */ 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 */ bool es_finished; /* true when ExecutorFinish is done */
List *es_exprcontexts; /* List of ExprContexts within EState */ List *es_exprcontexts; /* List of ExprContexts within EState */
......
...@@ -303,6 +303,7 @@ typedef enum NodeTag ...@@ -303,6 +303,7 @@ typedef enum NodeTag
T_DropdbStmt, T_DropdbStmt,
T_VacuumStmt, T_VacuumStmt,
T_ExplainStmt, T_ExplainStmt,
T_CreateTableAsStmt,
T_CreateSeqStmt, T_CreateSeqStmt,
T_AlterSeqStmt, T_AlterSeqStmt,
T_VariableSetStmt, T_VariableSetStmt,
......
...@@ -84,7 +84,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ ...@@ -84,7 +84,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */
/* /*
* Query - * 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. * for further processing by the rewriter and planner.
* *
* Utility statements (i.e. non-optimizable statements) have the * Utility statements (i.e. non-optimizable statements) have the
...@@ -111,8 +111,6 @@ typedef struct Query ...@@ -111,8 +111,6 @@ typedef struct Query
int resultRelation; /* rtable index of target relation for int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */ * INSERT/UPDATE/DELETE; 0 for SELECT */
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
bool hasAggs; /* has aggregates in tlist or havingQual */ bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */ bool hasWindowFuncs; /* has window functions in tlist */
bool hasSubLinks; /* has subquery SubLink */ bool hasSubLinks; /* has subquery SubLink */
...@@ -1009,7 +1007,7 @@ typedef struct SelectStmt ...@@ -1009,7 +1007,7 @@ typedef struct SelectStmt
*/ */
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */ * 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 *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */ List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */ Node *whereClause; /* WHERE qualification */
...@@ -2395,6 +2393,25 @@ typedef struct ExplainStmt ...@@ -2395,6 +2393,25 @@ typedef struct ExplainStmt
List *options; /* list of DefElem nodes */ List *options; /* list of DefElem nodes */
} ExplainStmt; } 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 * Checkpoint Statement
* ---------------------- * ----------------------
...@@ -2509,7 +2526,6 @@ typedef struct ExecuteStmt ...@@ -2509,7 +2526,6 @@ typedef struct ExecuteStmt
{ {
NodeTag type; NodeTag type;
char *name; /* The name of the plan to execute */ char *name; /* The name of the plan to execute */
IntoClause *into; /* Optional table to store results in */
List *params; /* Values to assign to parameters */ List *params; /* Values to assign to parameters */
} ExecuteStmt; } ExecuteStmt;
......
...@@ -54,8 +54,6 @@ typedef struct PlannedStmt ...@@ -54,8 +54,6 @@ typedef struct PlannedStmt
Node *utilityStmt; /* non-null if this is DECLARE CURSOR */ 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 */ List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */ Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */
......
...@@ -25,6 +25,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText, ...@@ -25,6 +25,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState, extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
CommonTableExpr *parentCTE, CommonTableExpr *parentCTE,
bool locked_from_parent); bool locked_from_parent);
extern Query *transformTopLevelStmt(ParseState *pstate, Node *parseTree);
extern Query *transformStmt(ParseState *pstate, Node *parseTree); extern Query *transformStmt(ParseState *pstate, Node *parseTree);
extern bool analyze_requires_snapshot(Node *parseTree); extern bool analyze_requires_snapshot(Node *parseTree);
......
...@@ -28,7 +28,7 @@ extern List *FetchPortalTargetList(Portal portal); ...@@ -28,7 +28,7 @@ extern List *FetchPortalTargetList(Portal portal);
extern List *FetchStatementTargetList(Node *stmt); extern List *FetchStatementTargetList(Node *stmt);
extern void PortalStart(Portal portal, ParamListInfo params, extern void PortalStart(Portal portal, ParamListInfo params,
bool use_active_snapshot); int eflags, bool use_active_snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats, extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats); int16 *formats);
......
...@@ -34,6 +34,8 @@ extern bool UtilityReturnsTuples(Node *parsetree); ...@@ -34,6 +34,8 @@ extern bool UtilityReturnsTuples(Node *parsetree);
extern TupleDesc UtilityTupleDescriptor(Node *parsetree); extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
extern Query *UtilityContainsQuery(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree); extern const char *CreateCommandTag(Node *parsetree);
extern LogStmtLevel GetCommandLogLevel(Node *parsetree); extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
......
...@@ -3291,24 +3291,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, ...@@ -3291,24 +3291,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
* We want to disallow SELECT INTO for now, because its behavior * We want to disallow SELECT INTO for now, because its behavior
* is not consistent with SELECT INTO in a normal plpgsql context. * is not consistent with SELECT INTO in a normal plpgsql context.
* (We need to reimplement EXECUTE to parse the string as a * (We need to reimplement EXECUTE to parse the string as a
* plpgsql command, not just feed it to SPI_execute.) However, * plpgsql command, not just feed it to SPI_execute.) This is not
* CREATE AS should be allowed ... and since it produces the same * a functional limitation because CREATE TABLE AS is allowed.
* parsetree as SELECT INTO, there's no way to tell the difference
* except to look at the source text. Wotta kluge!
*/ */
{ ereport(ERROR,
char *ptr; (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE of SELECT ... INTO is not implemented"),
for (ptr = querystr; *ptr; ptr++) errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
if (!scanner_isspace(*ptr)) break;
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;
}
/* Some SPI errors deserve specific error messages */ /* Some SPI errors deserve specific error messages */
case SPI_ERROR_COPY: case SPI_ERROR_COPY:
...@@ -5759,7 +5749,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr) ...@@ -5759,7 +5749,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
*/ */
if (!IsA(query, Query)) if (!IsA(query, Query))
return; return;
if (query->commandType != CMD_SELECT || query->intoClause) if (query->commandType != CMD_SELECT)
return; return;
if (query->rtable != NIL) if (query->rtable != NIL)
return; return;
...@@ -5833,7 +5823,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan) ...@@ -5833,7 +5823,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
*/ */
if (!IsA(stmt, PlannedStmt)) if (!IsA(stmt, PlannedStmt))
return; return;
if (stmt->commandType != CMD_SELECT || stmt->intoClause) if (stmt->commandType != CMD_SELECT)
return; return;
plan = stmt->planTree; plan = stmt->planTree;
if (!IsA(plan, Result)) if (!IsA(plan, Result))
......
...@@ -75,3 +75,22 @@ SELECT * FROM created_table; ...@@ -75,3 +75,22 @@ SELECT * FROM created_table;
(5 rows) (5 rows)
DROP TABLE created_table; 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;
^
...@@ -139,7 +139,7 @@ SELECT * FROM writetest, temptest; -- ok ...@@ -139,7 +139,7 @@ SELECT * FROM writetest, temptest; -- ok
(0 rows) (0 rows)
CREATE TABLE test AS SELECT * FROM writetest; -- fail 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; START TRANSACTION READ WRITE;
DROP TABLE writetest; -- ok DROP TABLE writetest; -- ok
COMMIT; COMMIT;
......
...@@ -67,3 +67,12 @@ SELECT make_table(); ...@@ -67,3 +67,12 @@ SELECT make_table();
SELECT * FROM created_table; SELECT * FROM created_table;
DROP TABLE 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment