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
This commit is contained in:
Tom Lane 2012-03-19 21:37:19 -04:00
parent 77503a7638
commit 9dbf2b7d75
47 changed files with 963 additions and 729 deletions

View File

@ -13,7 +13,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \
foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \

View File

@ -1210,15 +1210,17 @@ BeginCopy(bool is_from,
elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
/* Query mustn't use INTO, either */
if (query->intoClause)
/* The grammar allows SELECT INTO, but we don't support that */
if (query->utilityStmt != NULL &&
IsA(query->utilityStmt, CreateTableAsStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
Assert(query->commandType == CMD_SELECT);
Assert(query->utilityStmt == NULL);
/* plan the query */
plan = planner(query, 0, NULL);

View File

@ -0,0 +1,423 @@
/*-------------------------------------------------------------------------
*
* createas.c
* Execution of CREATE TABLE ... AS, a/k/a SELECT INTO
*
* We implement this by diverting the query's normal output to a
* specialized DestReceiver type.
*
* Formerly, this command was implemented as a variant of SELECT, which led
* to assorted legacy behaviors that we still try to preserve, notably that
* we must return a tuples-processed count in the completionTag.
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/commands/createas.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/xact.h"
#include "catalog/toasting.h"
#include "commands/createas.h"
#include "commands/prepare.h"
#include "commands/tablecmds.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteHandler.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
IntoClause *into; /* target relation specification */
/* These fields are filled by intorel_startup: */
Relation rel; /* relation to write to */
CommandId output_cid; /* cmin to insert in output tuples */
int hi_options; /* heap_insert performance options */
BulkInsertState bistate; /* bulk insert state */
} DR_intorel;
static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
static void intorel_shutdown(DestReceiver *self);
static void intorel_destroy(DestReceiver *self);
/*
* ExecCreateTableAs -- execute a CREATE TABLE AS command
*/
void
ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
ParamListInfo params, char *completionTag)
{
Query *query = (Query *) stmt->query;
IntoClause *into = stmt->into;
DestReceiver *dest;
List *rewritten;
PlannedStmt *plan;
QueryDesc *queryDesc;
ScanDirection dir;
/*
* Create the tuple receiver object and insert info it will need
*/
dest = CreateIntoRelDestReceiver(into);
/*
* The contained Query could be a SELECT, or an EXECUTE utility command.
* If the latter, we just pass it off to ExecuteQuery.
*/
Assert(IsA(query, Query));
if (query->commandType == CMD_UTILITY &&
IsA(query->utilityStmt, ExecuteStmt))
{
ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt;
ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
return;
}
Assert(query->commandType == CMD_SELECT);
/*
* Parse analysis was done already, but we still have to run the rule
* rewriter. We do not do AcquireRewriteLocks: we assume the query either
* came straight from the parser, or suitable locks were acquired by
* plancache.c.
*
* Because the rewriter and planner tend to scribble on the input, we make
* a preliminary copy of the source querytree. This prevents problems in
* the case that CTAS is in a portal or plpgsql function and is executed
* repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
*/
rewritten = QueryRewrite((Query *) copyObject(stmt->query));
/* SELECT should never rewrite to more or less than one SELECT query */
if (list_length(rewritten) != 1)
elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
/* plan the query */
plan = pg_plan_query(query, 0, params);
/*
* Use a snapshot with an updated command ID to ensure this query sees
* results of any previously executed queries. (This could only matter
* if the planner executed an allegedly-stable function that changed
* the database contents, but let's do it anyway to be parallel to the
* EXPLAIN code path.)
*/
PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId();
/* Create a QueryDesc, redirecting output to our tuple receiver */
queryDesc = CreateQueryDesc(plan, queryString,
GetActiveSnapshot(), InvalidSnapshot,
dest, params, 0);
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, GetIntoRelEFlags(into));
/*
* Normally, we run the plan to completion; but if skipData is specified,
* just do tuple receiver startup and shutdown.
*/
if (into->skipData)
dir = NoMovementScanDirection;
else
dir = ForwardScanDirection;
/* run the plan */
ExecutorRun(queryDesc, dir, 0L);
/* save the rowcount if we're given a completionTag to fill */
if (completionTag)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"SELECT %u", queryDesc->estate->es_processed);
/* and clean up */
ExecutorFinish(queryDesc);
ExecutorEnd(queryDesc);
FreeQueryDesc(queryDesc);
PopActiveSnapshot();
}
/*
* GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
*
* This is exported because EXPLAIN and PREPARE need it too. (Note: those
* callers still need to deal explicitly with the skipData flag; since they
* use different methods for suppressing execution, it doesn't seem worth
* trying to encapsulate that part.)
*/
int
GetIntoRelEFlags(IntoClause *intoClause)
{
/*
* We need to tell the executor whether it has to produce OIDs or not,
* because it doesn't have enough information to do so itself (since we
* can't build the target relation until after ExecutorStart).
*/
if (interpretOidsOption(intoClause->options))
return EXEC_FLAG_WITH_OIDS;
else
return EXEC_FLAG_WITHOUT_OIDS;
}
/*
* CreateIntoRelDestReceiver -- create a suitable DestReceiver object
*
* intoClause will be NULL if called from CreateDestReceiver(), in which
* case it has to be provided later. However, it is convenient to allow
* self->into to be filled in immediately for other callers.
*/
DestReceiver *
CreateIntoRelDestReceiver(IntoClause *intoClause)
{
DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
self->pub.receiveSlot = intorel_receive;
self->pub.rStartup = intorel_startup;
self->pub.rShutdown = intorel_shutdown;
self->pub.rDestroy = intorel_destroy;
self->pub.mydest = DestIntoRel;
self->into = intoClause;
/* other private fields will be set during intorel_startup */
return (DestReceiver *) self;
}
/*
* intorel_startup --- executor startup
*/
static void
intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
{
DR_intorel *myState = (DR_intorel *) self;
IntoClause *into = myState->into;
CreateStmt *create;
Oid intoRelationId;
Relation intoRelationDesc;
RangeTblEntry *rte;
Datum toast_options;
ListCell *lc;
int attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Assert(into != NULL); /* else somebody forgot to set it */
/*
* Create the target relation by faking up a CREATE TABLE parsetree and
* passing it to DefineRelation.
*/
create = makeNode(CreateStmt);
create->relation = into->rel;
create->tableElts = NIL; /* will fill below */
create->inhRelations = NIL;
create->ofTypename = NULL;
create->constraints = NIL;
create->options = into->options;
create->oncommit = into->onCommit;
create->tablespacename = into->tableSpaceName;
create->if_not_exists = false;
/*
* Build column definitions using "pre-cooked" type and collation info.
* If a column name list was specified in CREATE TABLE AS, override the
* column names derived from the query. (Too few column names are OK, too
* many are not.)
*/
lc = list_head(into->colNames);
for (attnum = 0; attnum < typeinfo->natts; attnum++)
{
Form_pg_attribute attribute = typeinfo->attrs[attnum];
ColumnDef *col = makeNode(ColumnDef);
TypeName *coltype = makeNode(TypeName);
if (lc)
{
col->colname = strVal(lfirst(lc));
lc = lnext(lc);
}
else
col->colname = NameStr(attribute->attname);
col->typeName = coltype;
col->inhcount = 0;
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
col->collClause = NULL;
col->collOid = attribute->attcollation;
col->constraints = NIL;
col->fdwoptions = NIL;
coltype->names = NIL;
coltype->typeOid = attribute->atttypid;
coltype->setof = false;
coltype->pct_type = false;
coltype->typmods = NIL;
coltype->typemod = attribute->atttypmod;
coltype->arrayBounds = NIL;
coltype->location = -1;
/*
* It's possible that the column is of a collatable type but the
* collation could not be resolved, so double-check. (We must
* check this here because DefineRelation would adopt the type's
* default collation rather than complaining.)
*/
if (!OidIsValid(col->collOid) &&
type_is_collatable(coltype->typeOid))
ereport(ERROR,
(errcode(ERRCODE_INDETERMINATE_COLLATION),
errmsg("no collation was derived for column \"%s\" with collatable type %s",
col->colname, format_type_be(coltype->typeOid)),
errhint("Use the COLLATE clause to set the collation explicitly.")));
create->tableElts = lappend(create->tableElts, col);
}
if (lc != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE TABLE AS specifies too many column names")));
/*
* Actually create the target table
*/
intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
/*
* If necessary, create a TOAST table for the target table. Note that
* AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
* the TOAST table will be visible for insertion.
*/
CommandCounterIncrement();
/* parse and validate reloptions for the toast table */
toast_options = transformRelOptions((Datum) 0,
create->options,
"toast",
validnsps,
true, false);
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
AlterTableCreateToastTable(intoRelationId, toast_options);
/*
* Finally we can open the target table
*/
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
/*
* Check INSERT permission on the constructed table.
*
* XXX: It would arguably make sense to skip this check if into->skipData
* is true.
*/
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = intoRelationId;
rte->relkind = RELKIND_RELATION;
rte->requiredPerms = ACL_INSERT;
for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)
rte->modifiedCols = bms_add_member(rte->modifiedCols,
attnum - FirstLowInvalidHeapAttributeNumber);
ExecCheckRTPerms(list_make1(rte), true);
/*
* Fill private fields of myState for use by later routines
*/
myState->rel = intoRelationDesc;
myState->output_cid = GetCurrentCommandId(true);
/*
* We can skip WAL-logging the insertions, unless PITR or streaming
* replication is in use. We can skip the FSM in any case.
*/
myState->hi_options = HEAP_INSERT_SKIP_FSM |
(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
}
/*
* intorel_receive --- receive one tuple
*/
static void
intorel_receive(TupleTableSlot *slot, DestReceiver *self)
{
DR_intorel *myState = (DR_intorel *) self;
HeapTuple tuple;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
*/
tuple = ExecMaterializeSlot(slot);
/*
* force assignment of new OID (see comments in ExecInsert)
*/
if (myState->rel->rd_rel->relhasoids)
HeapTupleSetOid(tuple, InvalidOid);
heap_insert(myState->rel,
tuple,
myState->output_cid,
myState->hi_options,
myState->bistate);
/* We know this is a newly created relation, so there are no indexes */
}
/*
* intorel_shutdown --- executor end
*/
static void
intorel_shutdown(DestReceiver *self)
{
DR_intorel *myState = (DR_intorel *) self;
FreeBulkInsertState(myState->bistate);
/* If we skipped using WAL, must heap_sync before commit */
if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
heap_sync(myState->rel);
/* close rel, but keep lock until commit */
heap_close(myState->rel, NoLock);
myState->rel = NULL;
}
/*
* intorel_destroy --- release DestReceiver object
*/
static void
intorel_destroy(DestReceiver *self)
{
pfree(self);
}

View File

@ -15,6 +15,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/createas.h"
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/hashjoin.h"
@ -45,7 +46,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
#define X_CLOSE_IMMEDIATE 2
#define X_NOWHITESPACE 4
static void ExplainOneQuery(Query *query, ExplainState *es,
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params);
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
ExplainState *es);
@ -212,7 +213,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
/* Explain every plan */
foreach(l, rewritten)
{
ExplainOneQuery((Query *) lfirst(l), &es, queryString, params);
ExplainOneQuery((Query *) lfirst(l), NULL, &es,
queryString, params);
/* Separate plans with an appropriate separator */
if (lnext(l) != NULL)
@ -288,21 +290,23 @@ ExplainResultDesc(ExplainStmt *stmt)
/*
* ExplainOneQuery -
* print out the execution plan for one Query
*
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
*/
static void
ExplainOneQuery(Query *query, ExplainState *es,
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
ExplainOneUtility(query->utilityStmt, es, queryString, params);
ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
return;
}
/* if an advisor plugin is present, let it manage things */
if (ExplainOneQuery_hook)
(*ExplainOneQuery_hook) (query, es, queryString, params);
(*ExplainOneQuery_hook) (query, into, es, queryString, params);
else
{
PlannedStmt *plan;
@ -311,7 +315,7 @@ ExplainOneQuery(Query *query, ExplainState *es,
plan = pg_plan_query(query, 0, params);
/* run it (if needed) and produce output */
ExplainOnePlan(plan, es, queryString, params);
ExplainOnePlan(plan, into, es, queryString, params);
}
}
@ -321,18 +325,36 @@ ExplainOneQuery(Query *query, ExplainState *es,
* (In general, utility statements don't have plans, but there are some
* we treat as special cases)
*
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
*
* This is exported because it's called back from prepare.c in the
* EXPLAIN EXECUTE case
* EXPLAIN EXECUTE case.
*/
void
ExplainOneUtility(Node *utilityStmt, ExplainState *es,
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
if (utilityStmt == NULL)
return;
if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, es,
if (IsA(utilityStmt, CreateTableAsStmt))
{
/*
* We have to rewrite the contained SELECT and then pass it back
* to ExplainOneQuery. It's probably not really necessary to copy
* the contained parsetree another time, but let's be safe.
*/
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
List *rewritten;
Assert(IsA(ctas->query, Query));
rewritten = QueryRewrite((Query *) copyObject(ctas->query));
Assert(list_length(rewritten) == 1);
ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
queryString, params);
}
else if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
queryString, params);
else if (IsA(utilityStmt, NotifyStmt))
{
@ -356,6 +378,9 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
* given a planned query, execute it if needed, and then print
* EXPLAIN output
*
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
* in which case executing the query should result in creating that table.
*
* Since we ignore any DeclareCursorStmt that might be attached to the query,
* if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
* query. This is different from pre-8.3 behavior but seems more useful than
@ -366,9 +391,10 @@ ExplainOneUtility(Node *utilityStmt, ExplainState *es,
* to call it.
*/
void
ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
DestReceiver *dest;
QueryDesc *queryDesc;
instr_time starttime;
double totaltime = 0;
@ -392,16 +418,27 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
PushCopiedSnapshot(GetActiveSnapshot());
UpdateActiveSnapshotCommandId();
/* Create a QueryDesc requesting no output */
/*
* Normally we discard the query's output, but if explaining CREATE TABLE
* AS, we'd better use the appropriate tuple receiver.
*/
if (into)
dest = CreateIntoRelDestReceiver(into);
else
dest = None_Receiver;
/* Create a QueryDesc for the query */
queryDesc = CreateQueryDesc(plannedstmt, queryString,
GetActiveSnapshot(), InvalidSnapshot,
None_Receiver, params, instrument_option);
dest, params, instrument_option);
/* Select execution options */
if (es->analyze)
eflags = 0; /* default run-to-completion flags */
else
eflags = EXEC_FLAG_EXPLAIN_ONLY;
if (into)
eflags |= GetIntoRelEFlags(into);
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, eflags);
@ -409,8 +446,16 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
/* Execute the plan for statistics if asked for */
if (es->analyze)
{
ScanDirection dir;
/* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
if (into && into->skipData)
dir = NoMovementScanDirection;
else
dir = ForwardScanDirection;
/* run the plan */
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
ExecutorRun(queryDesc, dir, 0L);
/* run cleanup too */
ExecutorFinish(queryDesc);

View File

@ -121,7 +121,7 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
/*
* Start execution, inserting parameters if any.
*/
PortalStart(portal, params, true);
PortalStart(portal, params, 0, true);
Assert(portal->strategy == PORTAL_ONE_SELECT);

View File

@ -18,6 +18,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/createas.h"
#include "commands/prepare.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
@ -170,7 +171,12 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
}
/*
* Implements the 'EXECUTE' utility statement.
* ExecuteQuery --- implement the 'EXECUTE' utility statement.
*
* This code also supports CREATE TABLE ... AS EXECUTE. That case is
* indicated by passing a non-null intoClause. The DestReceiver is already
* set up correctly for CREATE TABLE AS, but we still have to make a few
* other adjustments here.
*
* Note: this is one of very few places in the code that needs to deal with
* two query strings at once. The passed-in queryString is that of the
@ -179,8 +185,8 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
* source is that of the original PREPARE.
*/
void
ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
ParamListInfo params,
ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag)
{
PreparedStatement *entry;
@ -190,6 +196,8 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
EState *estate = NULL;
Portal portal;
char *query_string;
int eflags;
long count;
/* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true);
@ -222,25 +230,27 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
entry->plansource->query_string);
/* Replan if needed, and increment plan refcount for portal */
cplan = GetCachedPlan(entry->plansource, paramLI, false);
plan_list = cplan->stmt_list;
/*
* For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query
* so that we can modify its destination (yech, but this has always been
* ugly). For regular EXECUTE we can just use the cached query, since the
* executor is read-only.
* For CREATE TABLE ... AS EXECUTE, we must verify that the prepared
* statement is one that produces tuples. Currently we insist that it be
* a plain old SELECT. In future we might consider supporting other
* things such as INSERT ... RETURNING, but there are a couple of issues
* to be settled first, notably how WITH NO DATA should be handled in such
* a case (do we really want to suppress execution?) and how to pass down
* the OID-determining eflags (PortalStart won't handle them in such a
* case, and for that matter it's not clear the executor will either).
*
* For CREATE TABLE ... AS EXECUTE, we also have to ensure that the
* proper eflags and fetch count are passed to PortalStart/PortalRun.
*/
if (stmt->into)
if (intoClause)
{
MemoryContext oldContext;
PlannedStmt *pstmt;
/* Replan if needed, and increment plan refcount transiently */
cplan = GetCachedPlan(entry->plansource, paramLI, true);
/* Copy plan into portal's context, and modify */
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
plan_list = copyObject(cplan->stmt_list);
if (list_length(plan_list) != 1)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@ -252,20 +262,21 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
pstmt->intoClause = copyObject(stmt->into);
MemoryContextSwitchTo(oldContext);
/* Set appropriate eflags */
eflags = GetIntoRelEFlags(intoClause);
/* We no longer need the cached plan refcount ... */
ReleaseCachedPlan(cplan, true);
/* ... and we don't want the portal to depend on it, either */
cplan = NULL;
/* And tell PortalRun whether to run to completion or not */
if (intoClause->skipData)
count = 0;
else
count = FETCH_ALL;
}
else
{
/* Replan if needed, and increment plan refcount for portal */
cplan = GetCachedPlan(entry->plansource, paramLI, false);
plan_list = cplan->stmt_list;
/* Plain old EXECUTE */
eflags = 0;
count = FETCH_ALL;
}
PortalDefineQuery(portal,
@ -276,11 +287,11 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
cplan);
/*
* Run the portal to completion.
* Run the portal as appropriate.
*/
PortalStart(portal, paramLI, true);
PortalStart(portal, paramLI, eflags, true);
(void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag);
(void) PortalRun(portal, count, false, dest, dest, completionTag);
PortalDrop(portal, false);
@ -615,11 +626,14 @@ DropAllPreparedStatements(void)
/*
* Implements the 'EXPLAIN EXECUTE' utility statement.
*
* "into" is NULL unless we are doing EXPLAIN CREATE TABLE AS EXECUTE,
* in which case executing the query should result in creating that table.
*
* Note: the passed-in queryString is that of the EXPLAIN EXECUTE,
* not the original PREPARE; we get the latter string from the plancache.
*/
void
ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
const char *queryString, ParamListInfo params)
{
PreparedStatement *entry;
@ -665,27 +679,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
if (IsA(pstmt, PlannedStmt))
{
if (execstmt->into)
{
if (pstmt->commandType != CMD_SELECT ||
pstmt->utilityStmt != NULL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("prepared statement is not a SELECT")));
/* Copy the stmt so we can modify it */
pstmt = copyObject(pstmt);
pstmt->intoClause = execstmt->into;
}
ExplainOnePlan(pstmt, es, query_string, paramLI);
}
ExplainOnePlan(pstmt, into, es, query_string, paramLI);
else
{
ExplainOneUtility((Node *) pstmt, es, query_string, paramLI);
}
ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */

View File

@ -439,9 +439,17 @@ DefineView(ViewStmt *stmt, const char *queryString)
/*
* The grammar should ensure that the result is a single SELECT Query.
* However, it doesn't forbid SELECT INTO, so we have to check for that.
*/
if (!IsA(viewParse, Query) ||
viewParse->commandType != CMD_SELECT)
if (!IsA(viewParse, Query))
elog(ERROR, "unexpected parse analysis result");
if (viewParse->utilityStmt != NULL &&
IsA(viewParse->utilityStmt, CreateTableAsStmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("views must not contain SELECT INTO")));
if (viewParse->commandType != CMD_SELECT ||
viewParse->utilityStmt != NULL)
elog(ERROR, "unexpected parse analysis result");
/*
@ -449,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString)
* DefineQueryRewrite(), but that function will complain about a bogus ON
* SELECT rule, and we'd rather the message complain about a view.
*/
if (viewParse->intoClause != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("views must not contain SELECT INTO")));
if (viewParse->hasModifyingCTE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),

View File

@ -37,27 +37,20 @@
*/
#include "postgres.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/toasting.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@ -90,12 +83,6 @@ static char *ExecBuildSlotValueDescription(TupleTableSlot *slot,
int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
static void OpenIntoRel(QueryDesc *queryDesc);
static void CloseIntoRel(QueryDesc *queryDesc);
static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
static void intorel_shutdown(DestReceiver *self);
static void intorel_destroy(DestReceiver *self);
/* end of local decls */
@ -174,11 +161,9 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_SELECT:
/*
* SELECT INTO, SELECT FOR UPDATE/SHARE and modifying CTEs need to
* mark tuples
* SELECT FOR UPDATE/SHARE and modifying CTEs need to mark tuples
*/
if (queryDesc->plannedstmt->intoClause != NULL ||
queryDesc->plannedstmt->rowMarks != NIL ||
if (queryDesc->plannedstmt->rowMarks != NIL ||
queryDesc->plannedstmt->hasModifyingCTE)
estate->es_output_cid = GetCurrentCommandId(true);
@ -309,13 +294,6 @@ standard_ExecutorRun(QueryDesc *queryDesc,
if (sendTuples)
(*dest->rStartup) (dest, operation, queryDesc->tupDesc);
/*
* if it's CREATE TABLE AS ... WITH NO DATA, skip plan execution
*/
if (estate->es_select_into &&
queryDesc->plannedstmt->intoClause->skipData)
direction = NoMovementScanDirection;
/*
* run plan
*/
@ -451,12 +429,6 @@ standard_ExecutorEnd(QueryDesc *queryDesc)
ExecEndPlan(queryDesc->planstate, estate);
/*
* Close the SELECT INTO relation if any
*/
if (estate->es_select_into)
CloseIntoRel(queryDesc);
/* do away with our snapshots */
UnregisterSnapshot(estate->es_snapshot);
UnregisterSnapshot(estate->es_crosscheck_snapshot);
@ -706,15 +678,6 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
{
ListCell *l;
/*
* CREATE TABLE AS or SELECT INTO?
*
* XXX should we allow this if the destination is temp? Considering that
* it would still require catalog changes, probably not.
*/
if (plannedstmt->intoClause != NULL)
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
/* Fail if write permissions are requested on any non-temp table */
foreach(l, plannedstmt->rtable)
{
@ -863,18 +826,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
estate->es_rowMarks = lappend(estate->es_rowMarks, erm);
}
/*
* Detect whether we're doing SELECT INTO. If so, set the es_into_oids
* flag appropriately so that the plan tree will be initialized with the
* correct tuple descriptors. (Other SELECT INTO stuff comes later.)
*/
estate->es_select_into = false;
if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
{
estate->es_select_into = true;
estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
}
/*
* Initialize the executor's tuple table to empty.
*/
@ -926,9 +877,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
planstate = ExecInitNode(plan, estate, eflags);
/*
* Get the tuple descriptor describing the type of tuples to return. (this
* is especially important if we are creating a relation with "SELECT
* INTO")
* Get the tuple descriptor describing the type of tuples to return.
*/
tupType = ExecGetResultType(planstate);
@ -968,16 +917,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
queryDesc->tupDesc = tupType;
queryDesc->planstate = planstate;
/*
* If doing SELECT INTO, initialize the "into" relation. We must wait
* till now so we have the "clean" result tuple type to create the new
* table from.
*
* If EXPLAIN, skip creating the "into" relation.
*/
if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY))
OpenIntoRel(queryDesc);
}
/*
@ -1230,7 +1169,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
/*
* ExecContextForcesOids
*
* This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO,
* This is pretty grotty: when doing INSERT, UPDATE, or CREATE TABLE AS,
* we need to ensure that result tuples have space for an OID iff they are
* going to be stored into a relation that has OIDs. In other contexts
* we are free to choose whether to leave space for OIDs in result tuples
@ -1255,9 +1194,9 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
* the ModifyTable node, so ModifyTable has to set es_result_relation_info
* while initializing each subplan.
*
* SELECT INTO is even uglier, because we don't have the INTO relation's
* descriptor available when this code runs; we have to look aside at a
* flag set by InitPlan().
* CREATE TABLE AS is even uglier, because we don't have the target relation's
* descriptor available when this code runs; we have to look aside at the
* flags passed to ExecutorStart().
*/
bool
ExecContextForcesOids(PlanState *planstate, bool *hasoids)
@ -1275,9 +1214,14 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids)
}
}
if (planstate->state->es_select_into)
if (planstate->state->es_top_eflags & EXEC_FLAG_WITH_OIDS)
{
*hasoids = planstate->state->es_into_oids;
*hasoids = true;
return true;
}
if (planstate->state->es_top_eflags & EXEC_FLAG_WITHOUT_OIDS)
{
*hasoids = false;
return true;
}
@ -2290,8 +2234,6 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
estate->es_rowMarks = parentestate->es_rowMarks;
estate->es_top_eflags = parentestate->es_top_eflags;
estate->es_instrument = parentestate->es_instrument;
estate->es_select_into = parentestate->es_select_into;
estate->es_into_oids = parentestate->es_into_oids;
/* es_auxmodifytables must NOT be copied */
/*
@ -2423,351 +2365,3 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
/*
* Support for SELECT INTO (a/k/a CREATE TABLE AS)
*
* We implement SELECT INTO by diverting SELECT's normal output with
* a specialized DestReceiver type.
*/
typedef struct
{
DestReceiver pub; /* publicly-known function pointers */
EState *estate; /* EState we are working with */
DestReceiver *origdest; /* QueryDesc's original receiver */
Relation rel; /* Relation to write to */
int hi_options; /* heap_insert performance options */
BulkInsertState bistate; /* bulk insert state */
} DR_intorel;
/*
* OpenIntoRel --- actually create the SELECT INTO target relation
*
* This also replaces QueryDesc->dest with the special DestReceiver for
* SELECT INTO. We assume that the correct result tuple type has already
* been placed in queryDesc->tupDesc.
*/
static void
OpenIntoRel(QueryDesc *queryDesc)
{
IntoClause *into = queryDesc->plannedstmt->intoClause;
EState *estate = queryDesc->estate;
TupleDesc intoTupDesc = queryDesc->tupDesc;
Relation intoRelationDesc;
char *intoName;
Oid namespaceId;
Oid tablespaceId;
Datum reloptions;
Oid intoRelationId;
DR_intorel *myState;
RangeTblEntry *rte;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Assert(into);
/*
* XXX This code needs to be kept in sync with DefineRelation(). Maybe we
* should try to use that function instead.
*/
/*
* Check consistency of arguments
*/
if (into->onCommit != ONCOMMIT_NOOP
&& into->rel->relpersistence != RELPERSISTENCE_TEMP)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
{
AclResult aclresult;
int i;
for (i = 0; i < intoTupDesc->natts; i++)
{
Oid atttypid = intoTupDesc->attrs[i]->atttypid;
aclresult = pg_type_aclcheck(atttypid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_TYPE,
format_type_be(atttypid));
}
}
/*
* If a column name list was specified in CREATE TABLE AS, override the
* column names derived from the query. (Too few column names are OK, too
* many are not.) It would probably be all right to scribble directly on
* the query's result tupdesc, but let's be safe and make a copy.
*/
if (into->colNames)
{
ListCell *lc;
intoTupDesc = CreateTupleDescCopy(intoTupDesc);
attnum = 1;
foreach(lc, into->colNames)
{
char *colname = strVal(lfirst(lc));
if (attnum > intoTupDesc->natts)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE TABLE AS specifies too many column names")));
namestrcpy(&(intoTupDesc->attrs[attnum - 1]->attname), colname);
attnum++;
}
}
/*
* Find namespace to create in, check its permissions, lock it against
* concurrent drop, and mark into->rel as RELPERSISTENCE_TEMP if the
* selected namespace is temporary.
*/
intoName = into->rel->relname;
namespaceId = RangeVarGetAndCheckCreationNamespace(into->rel, NoLock,
NULL);
/*
* Security check: disallow creating temp tables from security-restricted
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
if (into->rel->relpersistence == RELPERSISTENCE_TEMP
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("cannot create temporary table within security-restricted operation")));
/*
* Select tablespace to use. If not specified, use default tablespace
* (which may in turn default to database's default).
*/
if (into->tableSpaceName)
{
tablespaceId = get_tablespace_oid(into->tableSpaceName, false);
}
else
{
tablespaceId = GetDefaultTablespace(into->rel->relpersistence);
/* note InvalidOid is OK in this case */
}
/* Check permissions except when using the database's default space */
if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
{
AclResult aclresult;
aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
ACL_CREATE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
get_tablespace_name(tablespaceId));
}
/* Parse and validate any reloptions */
reloptions = transformRelOptions((Datum) 0,
into->options,
NULL,
validnsps,
true,
false);
(void) heap_reloptions(RELKIND_RELATION, reloptions, true);
/* Now we can actually create the new relation */
intoRelationId = heap_create_with_catalog(intoName,
namespaceId,
tablespaceId,
InvalidOid,
InvalidOid,
InvalidOid,
GetUserId(),
intoTupDesc,
NIL,
RELKIND_RELATION,
into->rel->relpersistence,
false,
false,
true,
0,
into->onCommit,
reloptions,
true,
allowSystemTableMods);
Assert(intoRelationId != InvalidOid);
/*
* Advance command counter so that the newly-created relation's catalog
* tuples will be visible to heap_open.
*/
CommandCounterIncrement();
/*
* If necessary, create a TOAST table for the INTO relation. Note that
* AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
* the TOAST table will be visible for insertion.
*/
reloptions = transformRelOptions((Datum) 0,
into->options,
"toast",
validnsps,
true,
false);
(void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true);
AlterTableCreateToastTable(intoRelationId, reloptions);
/*
* And open the constructed table for writing.
*/
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
/*
* Check INSERT permission on the constructed table.
*/
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = intoRelationId;
rte->relkind = RELKIND_RELATION;
rte->requiredPerms = ACL_INSERT;
for (attnum = 1; attnum <= intoTupDesc->natts; attnum++)
rte->modifiedCols = bms_add_member(rte->modifiedCols,
attnum - FirstLowInvalidHeapAttributeNumber);
ExecCheckRTPerms(list_make1(rte), true);
/*
* Now replace the query's DestReceiver with one for SELECT INTO
*/
myState = (DR_intorel *) CreateDestReceiver(DestIntoRel);
Assert(myState->pub.mydest == DestIntoRel);
myState->estate = estate;
myState->origdest = queryDesc->dest;
myState->rel = intoRelationDesc;
queryDesc->dest = (DestReceiver *) myState;
/*
* We can skip WAL-logging the insertions, unless PITR or streaming
* replication is in use. We can skip the FSM in any case.
*/
myState->hi_options = HEAP_INSERT_SKIP_FSM |
(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
myState->bistate = GetBulkInsertState();
/* Not using WAL requires smgr_targblock be initially invalid */
Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
}
/*
* CloseIntoRel --- clean up SELECT INTO at ExecutorEnd time
*/
static void
CloseIntoRel(QueryDesc *queryDesc)
{
DR_intorel *myState = (DR_intorel *) queryDesc->dest;
/*
* OpenIntoRel might never have gotten called, and we also want to guard
* against double destruction.
*/
if (myState && myState->pub.mydest == DestIntoRel)
{
FreeBulkInsertState(myState->bistate);
/* If we skipped using WAL, must heap_sync before commit */
if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
heap_sync(myState->rel);
/* close rel, but keep lock until commit */
heap_close(myState->rel, NoLock);
/* restore the receiver belonging to executor's caller */
queryDesc->dest = myState->origdest;
/* might as well invoke my destructor */
intorel_destroy((DestReceiver *) myState);
}
}
/*
* CreateIntoRelDestReceiver -- create a suitable DestReceiver object
*/
DestReceiver *
CreateIntoRelDestReceiver(void)
{
DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
self->pub.receiveSlot = intorel_receive;
self->pub.rStartup = intorel_startup;
self->pub.rShutdown = intorel_shutdown;
self->pub.rDestroy = intorel_destroy;
self->pub.mydest = DestIntoRel;
/* private fields will be set by OpenIntoRel */
return (DestReceiver *) self;
}
/*
* intorel_startup --- executor startup
*/
static void
intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
{
/* no-op */
}
/*
* intorel_receive --- receive one tuple
*/
static void
intorel_receive(TupleTableSlot *slot, DestReceiver *self)
{
DR_intorel *myState = (DR_intorel *) self;
HeapTuple tuple;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
* writable copy
*/
tuple = ExecMaterializeSlot(slot);
/*
* force assignment of new OID (see comments in ExecInsert)
*/
if (myState->rel->rd_rel->relhasoids)
HeapTupleSetOid(tuple, InvalidOid);
heap_insert(myState->rel,
tuple,
myState->estate->es_output_cid,
myState->hi_options,
myState->bistate);
/* We know this is a newly created relation, so there are no indexes */
}
/*
* intorel_shutdown --- executor end
*/
static void
intorel_shutdown(DestReceiver *self)
{
/* no-op */
}
/*
* intorel_destroy --- release DestReceiver object
*/
static void
intorel_destroy(DestReceiver *self)
{
pfree(self);
}

View File

@ -137,8 +137,6 @@ CreateExecutorState(void)
estate->es_top_eflags = 0;
estate->es_instrument = 0;
estate->es_select_into = false;
estate->es_into_oids = false;
estate->es_finished = false;
estate->es_exprcontexts = NIL;

View File

@ -535,7 +535,6 @@ init_execution_state(List *queryTree_list,
if (ps->commandType == CMD_SELECT &&
ps->utilityStmt == NULL &&
ps->intoClause == NULL &&
!ps->hasModifyingCTE)
fcache->lazyEval = lasttages->lazyEval = true;
}
@ -1493,8 +1492,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
*/
if (parse &&
parse->commandType == CMD_SELECT &&
parse->utilityStmt == NULL &&
parse->intoClause == NULL)
parse->utilityStmt == NULL)
{
tlist_ptr = &parse->targetList;
tlist = parse->targetList;

View File

@ -1284,11 +1284,11 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
* Start portal execution.
*/
if (read_only)
PortalStart(portal, paramLI, true);
PortalStart(portal, paramLI, 0, true);
else
{
CommandCounterIncrement();
PortalStart(portal, paramLI, false);
PortalStart(portal, paramLI, 0, false);
}
Assert(portal->strategy != PORTAL_MULTI_QUERY);
@ -1907,17 +1907,39 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
}
else
{
char completionTag[COMPLETION_TAG_BUFSIZE];
ProcessUtility(stmt,
plansource->query_string,
paramLI,
false, /* not top level */
dest,
NULL);
completionTag);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced -
_SPI_current->tuptable->free;
res = SPI_OK_UTILITY;
/*
* CREATE TABLE AS is a messy special case for historical
* reasons. We must set _SPI_current->processed even though
* the tuples weren't returned to the caller, and we must
* return a special result code if the statement was spelled
* SELECT INTO.
*/
if (IsA(stmt, CreateTableAsStmt))
{
Assert(strncmp(completionTag, "SELECT ", 7) == 0);
_SPI_current->processed = strtoul(completionTag + 7,
NULL, 10);
if (((CreateTableAsStmt *) stmt)->is_select_into)
res = SPI_OK_SELINTO;
else
res = SPI_OK_UTILITY;
}
else
res = SPI_OK_UTILITY;
}
/*
@ -2042,9 +2064,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount)
{
case CMD_SELECT:
Assert(queryDesc->plannedstmt->utilityStmt == NULL);
if (queryDesc->plannedstmt->intoClause) /* select into table? */
res = SPI_OK_SELINTO;
else if (queryDesc->dest->mydest != DestSPI)
if (queryDesc->dest->mydest != DestSPI)
{
/* Don't return SPI_OK_SELECT if we're discarding result */
res = SPI_OK_UTILITY;

View File

@ -86,7 +86,6 @@ _copyPlannedStmt(const PlannedStmt *from)
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(utilityStmt);
COPY_NODE_FIELD(intoClause);
COPY_NODE_FIELD(subplans);
COPY_BITMAPSET_FIELD(rewindPlanIDs);
COPY_NODE_FIELD(rowMarks);
@ -2406,7 +2405,6 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(utilityStmt);
COPY_SCALAR_FIELD(resultRelation);
COPY_NODE_FIELD(intoClause);
COPY_SCALAR_FIELD(hasAggs);
COPY_SCALAR_FIELD(hasWindowFuncs);
COPY_SCALAR_FIELD(hasSubLinks);
@ -3194,6 +3192,18 @@ _copyExplainStmt(const ExplainStmt *from)
return newnode;
}
static CreateTableAsStmt *
_copyCreateTableAsStmt(const CreateTableAsStmt *from)
{
CreateTableAsStmt *newnode = makeNode(CreateTableAsStmt);
COPY_NODE_FIELD(query);
COPY_NODE_FIELD(into);
COPY_SCALAR_FIELD(is_select_into);
return newnode;
}
static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from)
{
@ -3602,7 +3612,6 @@ _copyExecuteStmt(const ExecuteStmt *from)
ExecuteStmt *newnode = makeNode(ExecuteStmt);
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(into);
COPY_NODE_FIELD(params);
return newnode;
@ -4234,6 +4243,9 @@ copyObject(const void *from)
case T_ExplainStmt:
retval = _copyExplainStmt(from);
break;
case T_CreateTableAsStmt:
retval = _copyCreateTableAsStmt(from);
break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;

View File

@ -900,7 +900,6 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(canSetTag);
COMPARE_NODE_FIELD(utilityStmt);
COMPARE_SCALAR_FIELD(resultRelation);
COMPARE_NODE_FIELD(intoClause);
COMPARE_SCALAR_FIELD(hasAggs);
COMPARE_SCALAR_FIELD(hasWindowFuncs);
COMPARE_SCALAR_FIELD(hasSubLinks);
@ -1564,6 +1563,16 @@ _equalExplainStmt(const ExplainStmt *a, const ExplainStmt *b)
return true;
}
static bool
_equalCreateTableAsStmt(const CreateTableAsStmt *a, const CreateTableAsStmt *b)
{
COMPARE_NODE_FIELD(query);
COMPARE_NODE_FIELD(into);
COMPARE_SCALAR_FIELD(is_select_into);
return true;
}
static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
@ -1908,7 +1917,6 @@ static bool
_equalExecuteStmt(const ExecuteStmt *a, const ExecuteStmt *b)
{
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(into);
COMPARE_NODE_FIELD(params);
return true;
@ -2793,6 +2801,9 @@ equal(const void *a, const void *b)
case T_ExplainStmt:
retval = _equalExplainStmt(a, b);
break;
case T_CreateTableAsStmt:
retval = _equalCreateTableAsStmt(a, b);
break;
case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b);
break;

View File

@ -250,7 +250,6 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node)
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(utilityStmt);
WRITE_NODE_FIELD(intoClause);
WRITE_NODE_FIELD(subplans);
WRITE_BITMAPSET_FIELD(rewindPlanIDs);
WRITE_NODE_FIELD(rowMarks);
@ -2181,7 +2180,6 @@ _outQuery(StringInfo str, const Query *node)
appendStringInfo(str, " :utilityStmt <>");
WRITE_INT_FIELD(resultRelation);
WRITE_NODE_FIELD(intoClause);
WRITE_BOOL_FIELD(hasAggs);
WRITE_BOOL_FIELD(hasWindowFuncs);
WRITE_BOOL_FIELD(hasSubLinks);

View File

@ -198,7 +198,6 @@ _readQuery(void)
READ_BOOL_FIELD(canSetTag);
READ_NODE_FIELD(utilityStmt);
READ_INT_FIELD(resultRelation);
READ_NODE_FIELD(intoClause);
READ_BOOL_FIELD(hasAggs);
READ_BOOL_FIELD(hasWindowFuncs);
READ_BOOL_FIELD(hasSubLinks);

View File

@ -233,7 +233,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->rtable = glob->finalrtable;
result->resultRelations = glob->resultRelations;
result->utilityStmt = parse->utilityStmt;
result->intoClause = parse->intoClause;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks;

View File

@ -22,6 +22,7 @@
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
#include "tcop/utility.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -1887,16 +1888,14 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
Query *query = (Query *) node;
ListCell *lc;
if (query->commandType == CMD_UTILITY)
while (query->commandType == CMD_UTILITY)
{
/* Ignore utility statements, except EXPLAIN */
if (IsA(query->utilityStmt, ExplainStmt))
{
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
Assert(IsA(query, Query));
Assert(query->commandType != CMD_UTILITY);
}
else
/*
* Ignore utility statements, except those (such as EXPLAIN) that
* contain a parsed-but-not-planned query.
*/
query = UtilityContainsQuery(query->utilityStmt);
if (query == NULL)
return false;
}

View File

@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query)
* are complex.
*/
if (query->commandType != CMD_SELECT ||
query->intoClause ||
query->setOperations ||
query->hasAggs ||
query->hasWindowFuncs ||

View File

@ -1136,8 +1136,7 @@ is_simple_subquery(Query *subquery)
*/
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
subquery->utilityStmt != NULL ||
subquery->intoClause != NULL)
subquery->utilityStmt != NULL)
elog(ERROR, "subquery is bogus");
/*
@ -1223,8 +1222,7 @@ is_simple_union_all(Query *subquery)
/* Let's just make sure it's a valid subselect ... */
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
subquery->utilityStmt != NULL ||
subquery->intoClause != NULL)
subquery->utilityStmt != NULL)
elog(ERROR, "subquery is bogus");
/* Is it a set-operation query at all? */

View File

@ -4158,7 +4158,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
pstate->p_sourcetext = src;
sql_fn_parser_setup(pstate, pinfo);
querytree = transformStmt(pstate, linitial(raw_parsetree_list));
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
@ -4168,7 +4168,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
querytree->utilityStmt ||
querytree->intoClause ||
querytree->hasAggs ||
querytree->hasWindowFuncs ||
querytree->hasSubLinks ||
@ -4678,12 +4677,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
querytree = linitial(querytree_list);
/*
* The single command must be a regular results-returning SELECT.
* The single command must be a plain SELECT.
*/
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
querytree->utilityStmt ||
querytree->intoClause)
querytree->utilityStmt)
goto fail;
/*

View File

@ -10,8 +10,8 @@
* utility commands, no locks are obtained here (and if they were, we could
* not be sure we'd still have them at execution). Hence the general rule
* for utility commands is to just dump them into a Query node untransformed.
* DECLARE CURSOR and EXPLAIN are exceptions because they contain
* optimizable statements.
* DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they
* contain optimizable statements, which we should transform.
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformExplainStmt(ParseState *pstate,
ExplainStmt *stmt);
static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
@ -91,7 +93,7 @@ parse_analyze(Node *parseTree, const char *sourceText,
if (numParams > 0)
parse_fixed_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree);
query = transformTopLevelStmt(pstate, parseTree);
free_parsestate(pstate);
@ -118,7 +120,7 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText,
parse_variable_parameters(pstate, paramTypes, numParams);
query = transformStmt(pstate, parseTree);
query = transformTopLevelStmt(pstate, parseTree);
/* make sure all is well with parameter types */
check_variable_parameters(pstate, query);
@ -151,8 +153,52 @@ parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
}
/*
* transformStmt -
* transformTopLevelStmt -
* transform a Parse tree into a Query tree.
*
* The only thing we do here that we don't do in transformStmt() is to
* convert SELECT ... INTO into CREATE TABLE AS. Since utility statements
* aren't allowed within larger statements, this is only allowed at the top
* of the parse tree, and so we only try it before entering the recursive
* transformStmt() processing.
*/
Query *
transformTopLevelStmt(ParseState *pstate, Node *parseTree)
{
if (IsA(parseTree, SelectStmt))
{
SelectStmt *stmt = (SelectStmt *) parseTree;
/* If it's a set-operation tree, drill down to leftmost SelectStmt */
while (stmt && stmt->op != SETOP_NONE)
stmt = stmt->larg;
Assert(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL);
if (stmt->intoClause)
{
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ctas->query = parseTree;
ctas->into = stmt->intoClause;
ctas->is_select_into = true;
/*
* Remove the intoClause from the SelectStmt. This makes it safe
* for transformSelectStmt to complain if it finds intoClause set
* (implying that the INTO appeared in a disallowed place).
*/
stmt->intoClause = NULL;
parseTree = (Node *) ctas;
}
}
return transformStmt(pstate, parseTree);
}
/*
* transformStmt -
* recursively transform a Parse tree into a Query tree.
*/
Query *
transformStmt(ParseState *pstate, Node *parseTree)
@ -202,6 +248,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(ExplainStmt *) parseTree);
break;
case T_CreateTableAsStmt:
result = transformCreateTableAsStmt(pstate,
(CreateTableAsStmt *) parseTree);
break;
default:
/*
@ -258,6 +309,7 @@ analyze_requires_snapshot(Node *parseTree)
break;
case T_ExplainStmt:
case T_CreateTableAsStmt:
/* yes, because we must analyze the contained statement */
result = true;
break;
@ -459,17 +511,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
free_parsestate(sub_pstate);
/* The grammar should have produced a SELECT, but it might have INTO */
/* The grammar should have produced a SELECT */
if (!IsA(selectQuery, Query) ||
selectQuery->commandType != CMD_SELECT ||
selectQuery->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");
if (selectQuery->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INSERT ... SELECT cannot specify INTO"),
parser_errposition(pstate,
exprLocation((Node *) selectQuery->intoClause))));
/*
* Make the source be a subquery in the INSERT's rangetable, and add
@ -539,6 +585,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
int sublist_length = -1;
int i;
Assert(selectStmt->intoClause == NULL);
foreach(lc, selectStmt->valuesLists)
{
List *sublist = (List *) lfirst(lc);
@ -653,6 +701,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
List *valuesLists = selectStmt->valuesLists;
Assert(list_length(valuesLists) == 1);
Assert(selectStmt->intoClause == NULL);
/* Do basic expression transformation (same as a ROW() expr) */
exprList = transformExpressionList(pstate,
@ -886,6 +935,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
}
/* Complain if we get called from someplace where INTO is not allowed */
if (stmt->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT ... INTO is not allowed here"),
parser_errposition(pstate,
exprLocation((Node *) stmt->intoClause))));
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
pstate->p_locking_clause = stmt->lockingClause;
@ -963,9 +1020,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
pstate->p_windowdefs,
&qry->targetList);
/* SELECT INTO/CREATE TABLE AS spec is just passed through */
qry->intoClause = stmt->intoClause;
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
@ -1013,6 +1067,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
/* Most SELECT stuff doesn't apply in a VALUES clause */
Assert(stmt->distinctClause == NIL);
Assert(stmt->intoClause == NULL);
Assert(stmt->targetList == NIL);
Assert(stmt->fromClause == NIL);
Assert(stmt->whereClause == NULL);
@ -1185,9 +1240,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
/* CREATE TABLE AS spec is just passed through */
qry->intoClause = stmt->intoClause;
/*
* There mustn't have been any table references in the expressions, else
* strange things would happen, like Cartesian products of those tables
@ -1286,21 +1338,27 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
}
/*
* Find leftmost leaf SelectStmt; extract the one-time-only items from it
* and from the top-level node.
* Find leftmost leaf SelectStmt. We currently only need to do this in
* order to deliver a suitable error message if there's an INTO clause
* there, implying the set-op tree is in a context that doesn't allow
* INTO. (transformSetOperationTree would throw error anyway, but it
* seems worth the trouble to throw a different error for non-leftmost
* INTO, so we produce that error in transformSetOperationTree.)
*/
leftmostSelect = stmt->larg;
while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
leftmostSelect = leftmostSelect->larg;
Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
leftmostSelect->larg == NULL);
qry->intoClause = leftmostSelect->intoClause;
/* clear this to prevent complaints in transformSetOperationTree() */
leftmostSelect->intoClause = NULL;
if (leftmostSelect->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT ... INTO is not allowed here"),
parser_errposition(pstate,
exprLocation((Node *) leftmostSelect->intoClause))));
/*
* These are not one-time, exactly, but we want to process them here and
* We need to extract ORDER BY and other top-level clauses here and
* not let transformSetOperationTree() see them --- else it'll just
* recurse right back here!
*/
@ -2107,14 +2165,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
result->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (result->intoClause)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR cannot specify INTO"),
parser_errposition(pstate,
exprLocation((Node *) result->intoClause))));
/*
* We also disallow data-modifying WITH in a cursor. (This could be
* allowed, but the semantics of when the updates occur might be
@ -2170,6 +2220,29 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
{
Query *result;
/* transform contained query, allowing SELECT INTO */
stmt->query = (Node *) transformTopLevelStmt(pstate, stmt->query);
/* represent the command as a utility Query */
result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;
return result;
}
/*
* transformCreateTableAsStmt -
* transform a CREATE TABLE AS (or SELECT ... INTO) Statement
*
* As with EXPLAIN, transform the contained statement now.
*/
static Query *
transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
{
Query *result;
/* transform contained query */
stmt->query = (Node *) transformStmt(pstate, stmt->query);

View File

@ -124,7 +124,6 @@ static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
static List *extractArgTypes(List *parameters);
static SelectStmt *findLeftmostSelect(SelectStmt *node);
static void insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
Node *limitOffset, Node *limitCount,
@ -3044,31 +3043,27 @@ ExistingIndex: USING INDEX index_name { $$ = $3; }
;
/*
* Note: CREATE TABLE ... AS SELECT ... is just another spelling for
* SELECT ... INTO.
*/
/*****************************************************************************
*
* QUERY :
* CREATE TABLE relname AS SelectStmt [ WITH [NO] DATA ]
*
*
* Note: SELECT ... INTO is a now-deprecated alternative for this.
*
*****************************************************************************/
CreateAsStmt:
CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data
{
/*
* When the SelectStmt is a set-operation tree, we must
* stuff the INTO information into the leftmost component
* Select, because that's where analyze.c will expect
* to find it.
*/
SelectStmt *n = findLeftmostSelect((SelectStmt *) $6);
if (n->intoClause != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE TABLE AS cannot specify INTO"),
parser_errposition(exprLocation((Node *) n->intoClause))));
n->intoClause = $4;
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ctas->query = $6;
ctas->into = $4;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($7);
$$ = $6;
$$ = (Node *) ctas;
}
;
@ -8285,20 +8280,22 @@ ExecuteStmt: EXECUTE name execute_param_clause
ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $2;
n->params = $3;
n->into = NULL;
$$ = (Node *) n;
}
| CREATE OptTemp TABLE create_as_target AS
EXECUTE name execute_param_clause opt_with_data
{
CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
ExecuteStmt *n = makeNode(ExecuteStmt);
n->name = $7;
n->params = $8;
n->into = $4;
ctas->query = (Node *) n;
ctas->into = $4;
ctas->is_select_into = false;
/* cram additional flags into the IntoClause */
$4->rel->relpersistence = $2;
$4->skipData = !($9);
$$ = (Node *) n;
$$ = (Node *) ctas;
}
;
@ -12870,18 +12867,6 @@ extractArgTypes(List *parameters)
return result;
}
/* findLeftmostSelect()
* Find the leftmost component SelectStmt in a set-operation parsetree.
*/
static SelectStmt *
findLeftmostSelect(SelectStmt *node)
{
while (node && node->op != SETOP_NONE)
node = node->larg;
Assert(node && IsA(node, SelectStmt) && node->larg == NULL);
return node;
}
/* insertSelectOptions()
* Insert ORDER BY, etc into an already-constructed SelectStmt.
*

View File

@ -495,12 +495,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
query->commandType != CMD_SELECT ||
query->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery in FROM cannot have SELECT INTO"),
parser_errposition(pstate,
exprLocation((Node *) query->intoClause))));
/*
* The subquery cannot make use of any variables from FROM items created

View File

@ -253,13 +253,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
if (query->utilityStmt != NULL)
elog(ERROR, "unexpected utility statement in WITH");
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery in WITH cannot have SELECT INTO"),
parser_errposition(pstate,
exprLocation((Node *) query->intoClause))));
/*
* We disallow data-modifying WITH except at the top level of a query,
* because it's not clear when such a modification should be executed.

View File

@ -1408,12 +1408,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
qtree->commandType != CMD_SELECT ||
qtree->utilityStmt != NULL)
elog(ERROR, "unexpected non-SELECT command in SubLink");
if (qtree->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery cannot have SELECT INTO"),
parser_errposition(pstate,
exprLocation((Node *) qtree->intoClause))));
sublink->subselect = (Node *) qtree;

View File

@ -323,8 +323,7 @@ DefineQueryRewrite(char *rulename,
query = (Query *) linitial(action);
if (!is_instead ||
query->commandType != CMD_SELECT ||
query->utilityStmt != NULL ||
query->intoClause != NULL)
query->utilityStmt != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rules on SELECT must have action INSTEAD SELECT")));

View File

@ -31,7 +31,7 @@
#include "access/printtup.h"
#include "access/xact.h"
#include "commands/copy.h"
#include "executor/executor.h"
#include "commands/createas.h"
#include "executor/functions.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
@ -118,7 +118,7 @@ CreateDestReceiver(CommandDest dest)
return CreateTuplestoreDestReceiver();
case DestIntoRel:
return CreateIntoRelDestReceiver();
return CreateIntoRelDestReceiver(NULL);
case DestCopyOut:
return CreateCopyDestReceiver();

View File

@ -624,7 +624,7 @@ pg_analyze_and_rewrite_params(Node *parsetree,
pstate->p_sourcetext = query_string;
(*parserSetup) (pstate, parserSetupArg);
query = transformStmt(pstate, parsetree);
query = transformTopLevelStmt(pstate, parsetree);
free_parsestate(pstate);
@ -975,7 +975,7 @@ exec_simple_query(const char *query_string)
* end up being able to do this, keeping the parse/plan snapshot around
* until after we start the portal doesn't cost much.
*/
PortalStart(portal, NULL, snapshot_set);
PortalStart(portal, NULL, 0, snapshot_set);
/* Done with the snapshot used for parsing/planning */
if (snapshot_set)
@ -1709,7 +1709,7 @@ exec_bind_message(StringInfo input_message)
* for query execution (currently, reuse will only occur if
* PORTAL_ONE_SELECT mode is chosen).
*/
PortalStart(portal, params, snapshot_set);
PortalStart(portal, params, 0, snapshot_set);
/* Done with the snapshot used for parameter I/O and parsing/planning */
if (snapshot_set)

View File

@ -260,8 +260,7 @@ ChoosePortalStrategy(List *stmts)
if (query->canSetTag)
{
if (query->commandType == CMD_SELECT &&
query->utilityStmt == NULL &&
query->intoClause == NULL)
query->utilityStmt == NULL)
{
if (query->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH;
@ -285,8 +284,7 @@ ChoosePortalStrategy(List *stmts)
if (pstmt->canSetTag)
{
if (pstmt->commandType == CMD_SELECT &&
pstmt->utilityStmt == NULL &&
pstmt->intoClause == NULL)
pstmt->utilityStmt == NULL)
{
if (pstmt->hasModifyingCTE)
return PORTAL_ONE_MOD_WITH;
@ -395,8 +393,7 @@ FetchStatementTargetList(Node *stmt)
else
{
if (query->commandType == CMD_SELECT &&
query->utilityStmt == NULL &&
query->intoClause == NULL)
query->utilityStmt == NULL)
return query->targetList;
if (query->returningList)
return query->returningList;
@ -408,8 +405,7 @@ FetchStatementTargetList(Node *stmt)
PlannedStmt *pstmt = (PlannedStmt *) stmt;
if (pstmt->commandType == CMD_SELECT &&
pstmt->utilityStmt == NULL &&
pstmt->intoClause == NULL)
pstmt->utilityStmt == NULL)
return pstmt->planTree->targetlist;
if (pstmt->hasReturning)
return pstmt->planTree->targetlist;
@ -430,7 +426,6 @@ FetchStatementTargetList(Node *stmt)
ExecuteStmt *estmt = (ExecuteStmt *) stmt;
PreparedStatement *entry;
Assert(!estmt->into);
entry = FetchPreparedStatement(estmt->name, true);
return FetchPreparedStatementTargetList(entry);
}
@ -442,9 +437,15 @@ FetchStatementTargetList(Node *stmt)
* Prepare a portal for execution.
*
* Caller must already have created the portal, done PortalDefineQuery(),
* and adjusted portal options if needed. If parameters are needed by
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
* and adjusted portal options if needed.
*
* If parameters are needed by the query, they must be passed in "params"
* (caller is responsible for giving them appropriate lifetime).
*
* The caller can also provide an initial set of "eflags" to be passed to
* ExecutorStart (but note these can be modified internally, and they are
* currently only honored for PORTAL_ONE_SELECT portals). Most callers
* should simply pass zero.
*
* The use_active_snapshot parameter is currently used only for
* PORTAL_ONE_SELECT portals. If it is true, the active snapshot will
@ -456,14 +457,15 @@ FetchStatementTargetList(Node *stmt)
* tupdesc (if any) is known.
*/
void
PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot)
PortalStart(Portal portal, ParamListInfo params,
int eflags, bool use_active_snapshot)
{
Portal saveActivePortal;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext oldContext;
QueryDesc *queryDesc;
int eflags;
int myeflags;
AssertArg(PortalIsValid(portal));
AssertState(portal->status == PORTAL_DEFINED);
@ -517,17 +519,18 @@ PortalStart(Portal portal, ParamListInfo params, bool use_active_snapshot)
/*
* If it's a scrollable cursor, executor needs to support
* REWIND and backwards scan.
* REWIND and backwards scan, as well as whatever the caller
* might've asked for.
*/
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
eflags = EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
myeflags = eflags | EXEC_FLAG_REWIND | EXEC_FLAG_BACKWARD;
else
eflags = 0; /* default run-to-completion flags */
myeflags = eflags;
/*
* Call ExecutorStart to prepare the plan for execution
*/
ExecutorStart(queryDesc, eflags);
ExecutorStart(queryDesc, myeflags);
/*
* This tells PortalCleanup to shut down the executor

View File

@ -29,6 +29,7 @@
#include "commands/collationcmds.h"
#include "commands/conversioncmds.h"
#include "commands/copy.h"
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
@ -127,9 +128,7 @@ CommandIsReadOnly(Node *parsetree)
switch (stmt->commandType)
{
case CMD_SELECT:
if (stmt->intoClause != NULL)
return false; /* SELECT INTO */
else if (stmt->rowMarks != NIL)
if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */
else if (stmt->hasModifyingCTE)
return false; /* data-modifying CTE */
@ -198,6 +197,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateSchemaStmt:
case T_CreateSeqStmt:
case T_CreateStmt:
case T_CreateTableAsStmt:
case T_CreateTableSpaceStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
@ -673,7 +673,8 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_ExecuteStmt:
ExecuteQuery((ExecuteStmt *) parsetree, queryString, params,
ExecuteQuery((ExecuteStmt *) parsetree, NULL,
queryString, params,
dest, completionTag);
break;
@ -1036,6 +1037,11 @@ standard_ProcessUtility(Node *parsetree,
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
case T_CreateTableAsStmt:
ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, completionTag);
break;
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree);
break;
@ -1230,8 +1236,6 @@ UtilityReturnsTuples(Node *parsetree)
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry;
if (stmt->into)
return false;
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return false; /* not our business to raise error */
@ -1282,8 +1286,6 @@ UtilityTupleDescriptor(Node *parsetree)
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *entry;
if (stmt->into)
return NULL;
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return NULL; /* not our business to raise error */
@ -1317,9 +1319,8 @@ QueryReturnsTuples(Query *parsetree)
switch (parsetree->commandType)
{
case CMD_SELECT:
/* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */
if (parsetree->utilityStmt == NULL &&
parsetree->intoClause == NULL)
/* returns tuples ... unless it's DECLARE CURSOR */
if (parsetree->utilityStmt == NULL)
return true;
break;
case CMD_INSERT:
@ -1341,6 +1342,37 @@ QueryReturnsTuples(Query *parsetree)
#endif
/*
* UtilityContainsQuery
* Return the contained Query, or NULL if there is none
*
* Certain utility statements, such as EXPLAIN, contain a Query.
* This function encapsulates knowledge of exactly which ones do.
* We assume it is invoked only on already-parse-analyzed statements
* (else the contained parsetree isn't a Query yet).
*/
Query *
UtilityContainsQuery(Node *parsetree)
{
switch (nodeTag(parsetree))
{
case T_ExplainStmt:
Assert(IsA(((ExplainStmt *) parsetree)->query, Query));
return (Query *) ((ExplainStmt *) parsetree)->query;
case T_CreateTableAsStmt:
/* might or might not contain a Query ... */
if (IsA(((CreateTableAsStmt *) parsetree)->query, Query))
return (Query *) ((CreateTableAsStmt *) parsetree)->query;
Assert(IsA(((CreateTableAsStmt *) parsetree)->query, ExecuteStmt));
return NULL;
default:
return NULL;
}
}
/*
* AlterObjectTypeCommandTag
* helper function for CreateCommandTag
@ -1907,6 +1939,13 @@ CreateCommandTag(Node *parsetree)
tag = "EXPLAIN";
break;
case T_CreateTableAsStmt:
if (((CreateTableAsStmt *) parsetree)->is_select_into)
tag = "SELECT INTO";
else
tag = "CREATE TABLE AS";
break;
case T_VariableSetStmt:
switch (((VariableSetStmt *) parsetree)->kind)
{
@ -2060,8 +2099,6 @@ CreateCommandTag(Node *parsetree)
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR";
}
else if (stmt->intoClause != NULL)
tag = "SELECT INTO";
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
@ -2110,8 +2147,6 @@ CreateCommandTag(Node *parsetree)
Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
tag = "DECLARE CURSOR";
}
else if (stmt->intoClause != NULL)
tag = "SELECT INTO";
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
@ -2179,7 +2214,7 @@ GetCommandLogLevel(Node *parsetree)
case T_SelectStmt:
if (((SelectStmt *) parsetree)->intoClause)
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
lev = LOGSTMT_DDL; /* SELECT INTO */
else
lev = LOGSTMT_ALL;
break;
@ -2429,6 +2464,10 @@ GetCommandLogLevel(Node *parsetree)
}
break;
case T_CreateTableAsStmt:
lev = LOGSTMT_DDL;
break;
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
@ -2529,10 +2568,7 @@ GetCommandLogLevel(Node *parsetree)
switch (stmt->commandType)
{
case CMD_SELECT:
if (stmt->intoClause != NULL)
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
else
lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:
@ -2558,10 +2594,7 @@ GetCommandLogLevel(Node *parsetree)
switch (stmt->commandType)
{
case CMD_SELECT:
if (stmt->intoClause != NULL)
lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */
else
lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */
lev = LOGSTMT_ALL;
break;
case CMD_UPDATE:

View File

@ -1232,20 +1232,16 @@ AcquireExecutorLocks(List *stmt_list, bool acquire)
if (!IsA(plannedstmt, PlannedStmt))
{
/*
* Ignore utility statements, except EXPLAIN which contains a
* parsed-but-not-planned query. Note: it's okay to use
* Ignore utility statements, except those (such as EXPLAIN) that
* contain a parsed-but-not-planned query. Note: it's okay to use
* ScanQueryForLocks, even though the query hasn't been through
* rule rewriting, because rewriting doesn't change the query
* representation.
*/
if (IsA(plannedstmt, ExplainStmt))
{
Query *query;
Query *query = UtilityContainsQuery((Node *) plannedstmt);
query = (Query *) ((ExplainStmt *) plannedstmt)->query;
Assert(IsA(query, Query));
if (query)
ScanQueryForLocks(query, acquire);
}
continue;
}
@ -1304,13 +1300,10 @@ AcquirePlannerLocks(List *stmt_list, bool acquire)
if (query->commandType == CMD_UTILITY)
{
/* Ignore utility statements, except EXPLAIN */
if (IsA(query->utilityStmt, ExplainStmt))
{
query = (Query *) ((ExplainStmt *) query->utilityStmt)->query;
Assert(IsA(query, Query));
/* Ignore utility statements, unless they contain a Query */
query = UtilityContainsQuery(query->utilityStmt);
if (query)
ScanQueryForLocks(query, acquire);
}
continue;
}
@ -1648,9 +1641,9 @@ ResetPlanCache(void)
* aborted transactions when we can't revalidate them (cf bug #5269).
* In general there is no point in invalidating utility statements
* since they have no plans anyway. So invalidate it only if it
* contains at least one non-utility statement. (EXPLAIN counts as a
* non-utility statement, though, since it contains an analyzed query
* that might have dependencies.)
* contains at least one non-utility statement, or contains a utility
* statement that contains a pre-analyzed query (which could have
* dependencies.)
*/
foreach(lc, plansource->query_list)
{
@ -1658,12 +1651,13 @@ ResetPlanCache(void)
Assert(IsA(query, Query));
if (query->commandType != CMD_UTILITY ||
IsA(query->utilityStmt, ExplainStmt))
UtilityContainsQuery(query->utilityStmt))
{
/* non-utility statement, so invalidate */
plansource->is_valid = false;
if (plansource->gplan)
plansource->gplan->is_valid = false;
/* no need to look further */
break;
}
}

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201203111
#define CATALOG_VERSION_NO 201203191
#endif

View File

@ -0,0 +1,29 @@
/*-------------------------------------------------------------------------
*
* createas.h
* prototypes for createas.c.
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/commands/createas.h
*
*-------------------------------------------------------------------------
*/
#ifndef CREATEAS_H
#define CREATEAS_H
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
extern void ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
ParamListInfo params, char *completionTag);
extern int GetIntoRelEFlags(IntoClause *intoClause);
extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause);
#endif /* CREATEAS_H */

View File

@ -42,6 +42,7 @@ typedef struct ExplainState
/* Hook for plugins to get control in ExplainOneQuery() */
typedef void (*ExplainOneQuery_hook_type) (Query *query,
IntoClause *into,
ExplainState *es,
const char *queryString,
ParamListInfo params);
@ -59,10 +60,12 @@ extern void ExplainInitState(ExplainState *es);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
extern void ExplainOneUtility(Node *utilityStmt, ExplainState *es,
extern void ExplainOneUtility(Node *utilityStmt, IntoClause *into,
ExplainState *es,
const char *queryString, ParamListInfo params);
extern void ExplainOnePlan(PlannedStmt *plannedstmt, ExplainState *es,
extern void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into,
ExplainState *es,
const char *queryString, ParamListInfo params);
extern void ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc);

View File

@ -35,11 +35,12 @@ typedef struct
/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
extern void PrepareQuery(PrepareStmt *stmt, const char *queryString);
extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
ParamListInfo params,
extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag);
extern void DeallocateQuery(DeallocateStmt *stmt);
extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into,
ExplainState *es,
const char *queryString, ParamListInfo params);
/* Low-level access to stored prepared statements */
@ -52,6 +53,6 @@ extern void DropPreparedStatement(const char *stmt_name, bool showError);
extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
void DropAllPreparedStatements(void);
extern void DropAllPreparedStatements(void);
#endif /* PREPARE_H */

View File

@ -30,8 +30,8 @@
*
* EXPLAIN_ONLY indicates that the plan tree is being initialized just so
* EXPLAIN can print it out; it will not be run. Hence, no side-effects
* of startup should occur (such as creating a SELECT INTO target table).
* However, error checks (such as permission checks) should be performed.
* of startup should occur. However, error checks (such as permission checks)
* should be performed.
*
* REWIND indicates that the plan node should try to efficiently support
* rescans without parameter changes. (Nodes must support ExecReScan calls
@ -49,12 +49,18 @@
* AfterTriggerBeginQuery/AfterTriggerEndQuery. This does not necessarily
* mean that the plan can't queue any AFTER triggers; just that the caller
* is responsible for there being a trigger context for them to be queued in.
*
* WITH/WITHOUT_OIDS tell the executor to emit tuples with or without space
* for OIDs, respectively. These are currently used only for CREATE TABLE AS.
* If neither is set, the plan may or may not produce tuples including OIDs.
*/
#define EXEC_FLAG_EXPLAIN_ONLY 0x0001 /* EXPLAIN, no ANALYZE */
#define EXEC_FLAG_REWIND 0x0002 /* need efficient rescan */
#define EXEC_FLAG_BACKWARD 0x0004 /* need backward scan */
#define EXEC_FLAG_MARK 0x0008 /* need mark/restore */
#define EXEC_FLAG_SKIP_TRIGGERS 0x0010 /* skip AfterTrigger calls */
#define EXEC_FLAG_WITH_OIDS 0x0020 /* force OIDs in returned tuples */
#define EXEC_FLAG_WITHOUT_OIDS 0x0040 /* force no OIDs in returned tuples */
/*
@ -204,7 +210,6 @@ extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
extern TupleTableSlot *EvalPlanQualNext(EPQState *epqstate);
extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate);
extern void EvalPlanQualEnd(EPQState *epqstate);
extern DestReceiver *CreateIntoRelDestReceiver(void);
/*
* prototypes from functions in execProcnode.c

View File

@ -371,8 +371,6 @@ typedef struct EState
int es_top_eflags; /* eflags passed to ExecutorStart */
int es_instrument; /* OR of InstrumentOption flags */
bool es_select_into; /* true if doing SELECT INTO */
bool es_into_oids; /* true to generate OIDs in SELECT INTO */
bool es_finished; /* true when ExecutorFinish is done */
List *es_exprcontexts; /* List of ExprContexts within EState */

View File

@ -303,6 +303,7 @@ typedef enum NodeTag
T_DropdbStmt,
T_VacuumStmt,
T_ExplainStmt,
T_CreateTableAsStmt,
T_CreateSeqStmt,
T_AlterSeqStmt,
T_VariableSetStmt,

View File

@ -84,7 +84,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */
/*
* Query -
* Parse analysis turns all statements into a Query tree (via transformStmt)
* Parse analysis turns all statements into a Query tree
* for further processing by the rewriter and planner.
*
* Utility statements (i.e. non-optimizable statements) have the
@ -111,8 +111,6 @@ typedef struct Query
int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
bool hasSubLinks; /* has subquery SubLink */
@ -1009,7 +1007,7 @@ typedef struct SelectStmt
*/
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
@ -2395,6 +2393,25 @@ typedef struct ExplainStmt
List *options; /* list of DefElem nodes */
} ExplainStmt;
/* ----------------------
* CREATE TABLE AS Statement (a/k/a SELECT INTO)
*
* A query written as CREATE TABLE AS will produce this node type natively.
* A query written as SELECT ... INTO will be transformed to this form during
* parse analysis.
*
* The "query" field is handled similarly to EXPLAIN, though note that it
* can be a SELECT or an EXECUTE, but not other DML statements.
* ----------------------
*/
typedef struct CreateTableAsStmt
{
NodeTag type;
Node *query; /* the query (see comments above) */
IntoClause *into; /* destination table */
bool is_select_into; /* it was written as SELECT INTO */
} CreateTableAsStmt;
/* ----------------------
* Checkpoint Statement
* ----------------------
@ -2509,7 +2526,6 @@ typedef struct ExecuteStmt
{
NodeTag type;
char *name; /* The name of the plan to execute */
IntoClause *into; /* Optional table to store results in */
List *params; /* Values to assign to parameters */
} ExecuteStmt;

View File

@ -54,8 +54,6 @@ typedef struct PlannedStmt
Node *utilityStmt; /* non-null if this is DECLARE CURSOR */
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */

View File

@ -25,6 +25,8 @@ extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
CommonTableExpr *parentCTE,
bool locked_from_parent);
extern Query *transformTopLevelStmt(ParseState *pstate, Node *parseTree);
extern Query *transformStmt(ParseState *pstate, Node *parseTree);
extern bool analyze_requires_snapshot(Node *parseTree);

View File

@ -28,7 +28,7 @@ extern List *FetchPortalTargetList(Portal portal);
extern List *FetchStatementTargetList(Node *stmt);
extern void PortalStart(Portal portal, ParamListInfo params,
bool use_active_snapshot);
int eflags, bool use_active_snapshot);
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);

View File

@ -34,6 +34,8 @@ extern bool UtilityReturnsTuples(Node *parsetree);
extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
extern Query *UtilityContainsQuery(Node *parsetree);
extern const char *CreateCommandTag(Node *parsetree);
extern LogStmtLevel GetCommandLogLevel(Node *parsetree);

View File

@ -3291,24 +3291,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
* We want to disallow SELECT INTO for now, because its behavior
* is not consistent with SELECT INTO in a normal plpgsql context.
* (We need to reimplement EXECUTE to parse the string as a
* plpgsql command, not just feed it to SPI_execute.) However,
* CREATE AS should be allowed ... and since it produces the same
* parsetree as SELECT INTO, there's no way to tell the difference
* except to look at the source text. Wotta kluge!
* plpgsql command, not just feed it to SPI_execute.) This is not
* a functional limitation because CREATE TABLE AS is allowed.
*/
{
char *ptr;
for (ptr = querystr; *ptr; ptr++)
if (!scanner_isspace(*ptr))
break;
if (*ptr == 'S' || *ptr == 's')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE of SELECT ... INTO is not implemented"),
errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
break;
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE of SELECT ... INTO is not implemented"),
errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
break;
/* Some SPI errors deserve specific error messages */
case SPI_ERROR_COPY:
@ -5759,7 +5749,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
*/
if (!IsA(query, Query))
return;
if (query->commandType != CMD_SELECT || query->intoClause)
if (query->commandType != CMD_SELECT)
return;
if (query->rtable != NIL)
return;
@ -5833,7 +5823,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
*/
if (!IsA(stmt, PlannedStmt))
return;
if (stmt->commandType != CMD_SELECT || stmt->intoClause)
if (stmt->commandType != CMD_SELECT)
return;
plan = stmt->planTree;
if (!IsA(plan, Result))

View File

@ -75,3 +75,22 @@ SELECT * FROM created_table;
(5 rows)
DROP TABLE created_table;
--
-- Disallowed uses of SELECT ... INTO. All should fail
--
DECLARE foo CURSOR FOR SELECT 1 INTO b;
ERROR: SELECT ... INTO is not allowed here
LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;
^
COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
ERROR: COPY (SELECT INTO) is not supported
SELECT * FROM (SELECT 1 INTO f) bar;
ERROR: SELECT ... INTO is not allowed here
LINE 1: SELECT * FROM (SELECT 1 INTO f) bar;
^
CREATE VIEW foo AS SELECT 1 INTO b;
ERROR: views must not contain SELECT INTO
INSERT INTO b SELECT 1 INTO f;
ERROR: SELECT ... INTO is not allowed here
LINE 1: INSERT INTO b SELECT 1 INTO f;
^

View File

@ -139,7 +139,7 @@ SELECT * FROM writetest, temptest; -- ok
(0 rows)
CREATE TABLE test AS SELECT * FROM writetest; -- fail
ERROR: cannot execute SELECT INTO in a read-only transaction
ERROR: cannot execute CREATE TABLE AS in a read-only transaction
START TRANSACTION READ WRITE;
DROP TABLE writetest; -- ok
COMMIT;

View File

@ -67,3 +67,12 @@ SELECT make_table();
SELECT * FROM created_table;
DROP TABLE created_table;
--
-- Disallowed uses of SELECT ... INTO. All should fail
--
DECLARE foo CURSOR FOR SELECT 1 INTO b;
COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
SELECT * FROM (SELECT 1 INTO f) bar;
CREATE VIEW foo AS SELECT 1 INTO b;
INSERT INTO b SELECT 1 INTO f;