From bbbe825f5f46d7ead60502f43d3b414719a41aa5 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 27 Apr 2007 22:05:49 +0000 Subject: [PATCH] Modify processing of DECLARE CURSOR and EXPLAIN so that they can resolve the types of unspecified parameters when submitted via extended query protocol. This worked in 8.2 but I had broken it during plancache changes. DECLARE CURSOR is now treated almost exactly like a plain SELECT through parse analysis, rewrite, and planning; only just before sending to the executor do we divert it away to ProcessUtility. This requires a special-case check in a number of places, but practically all of them were already special-casing SELECT INTO, so it's not too ugly. (Maybe it would be a good idea to merge the two by treating IntoClause as a form of utility statement? Not going to worry about that now, though.) That approach doesn't work for EXPLAIN, however, so for that I punted and used a klugy solution of running parse analysis an extra time if under extended query protocol. --- src/backend/commands/copy.c | 5 +- src/backend/commands/explain.c | 67 ++------ src/backend/commands/portalcmds.c | 82 +++------- src/backend/commands/prepare.c | 24 ++- src/backend/executor/execMain.c | 10 +- src/backend/executor/functions.c | 20 ++- src/backend/executor/spi.c | 8 +- src/backend/nodes/copyfuncs.c | 9 +- src/backend/nodes/equalfuncs.c | 6 +- src/backend/nodes/outfuncs.c | 9 +- src/backend/nodes/readfuncs.c | 4 +- src/backend/optimizer/plan/planagg.c | 5 +- src/backend/optimizer/plan/planner.c | 10 +- src/backend/optimizer/prep/prepjointree.c | 8 +- src/backend/optimizer/util/clauses.c | 5 +- src/backend/parser/analyze.c | 178 ++++++++++++++++++---- src/backend/parser/gram.y | 8 +- src/backend/parser/parse_clause.c | 7 +- src/backend/parser/parse_expr.c | 5 +- src/backend/parser/parse_type.c | 4 +- src/backend/rewrite/rewriteDefine.c | 6 +- src/backend/tcop/pquery.c | 19 ++- src/backend/tcop/utility.c | 49 ++++-- src/include/commands/portalcmds.h | 5 +- src/include/nodes/parsenodes.h | 20 ++- src/include/nodes/plannodes.h | 6 +- 26 files changed, 333 insertions(+), 246 deletions(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index be33bb76c4..885411cf2c 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.282 2007/04/18 02:28:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.283 2007/04/27 22:05:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1015,9 +1015,10 @@ DoCopy(const CopyStmt *stmt, const char *queryString) query = (Query *) linitial(rewritten); Assert(query->commandType == CMD_SELECT); + Assert(query->utilityStmt == NULL); /* Query mustn't use INTO, either */ - if (query->into) + if (query->intoClause) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("COPY (SELECT INTO) is not supported"))); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index bb7d430359..592eeba417 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.161 2007/04/16 01:14:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.162 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,8 +42,8 @@ typedef struct ExplainState List *rtable; /* range table */ } ExplainState; -static void ExplainOneQuery(Query *query, int cursorOptions, - ExplainStmt *stmt, const char *queryString, +static void ExplainOneQuery(Query *query, ExplainStmt *stmt, + const char *queryString, ParamListInfo params, TupOutputState *tstate); static double elapsed_time(instr_time *starttime); static void explain_outNode(StringInfo str, @@ -102,8 +102,8 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, /* Explain every plan */ foreach(l, rewritten) { - ExplainOneQuery((Query *) lfirst(l), 0, - stmt, queryString, params, tstate); + ExplainOneQuery((Query *) lfirst(l), stmt, + queryString, params, tstate); /* put a blank line between plans */ if (lnext(l) != NULL) do_text_output_oneline(tstate, ""); @@ -134,8 +134,7 @@ ExplainResultDesc(ExplainStmt *stmt) * print out the execution plan for one Query */ static void -ExplainOneQuery(Query *query, int cursorOptions, - ExplainStmt *stmt, const char *queryString, +ExplainOneQuery(Query *query, ExplainStmt *stmt, const char *queryString, ParamListInfo params, TupOutputState *tstate) { PlannedStmt *plan; @@ -150,7 +149,7 @@ ExplainOneQuery(Query *query, int cursorOptions, } /* plan the query */ - plan = planner(query, cursorOptions, params); + plan = planner(query, 0, params); /* * Update snapshot command ID to ensure this query sees results of any @@ -187,52 +186,7 @@ ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt, if (utilityStmt == NULL) return; - if (IsA(utilityStmt, DeclareCursorStmt)) - { - DeclareCursorStmt *dcstmt = (DeclareCursorStmt *) utilityStmt; - Oid *param_types; - int num_params; - Query *query; - List *rewritten; - ExplainStmt newstmt; - - /* Convert parameter type data to the form parser wants */ - getParamListTypes(params, ¶m_types, &num_params); - - /* - * Run parse analysis and rewrite. Note this also acquires sufficient - * locks on the source table(s). - * - * Because the parser and planner tend to scribble on their input, we - * make a preliminary copy of the source querytree. This prevents - * problems in the case that the DECLARE CURSOR is in a portal or - * plpgsql function and is executed repeatedly. (See also the same - * hack in COPY and PREPARE.) XXX FIXME someday. - */ - rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query), - queryString, - param_types, num_params); - - /* We don't expect more or less than one result query */ - if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query)) - elog(ERROR, "unexpected rewrite result"); - query = (Query *) linitial(rewritten); - if (query->commandType != CMD_SELECT) - elog(ERROR, "unexpected rewrite result"); - - /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */ - if (query->into) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("DECLARE CURSOR cannot specify INTO"))); - - /* do not actually execute the underlying query! */ - memcpy(&newstmt, stmt, sizeof(ExplainStmt)); - newstmt.analyze = false; - ExplainOneQuery(query, dcstmt->options, &newstmt, - queryString, params, tstate); - } - else if (IsA(utilityStmt, ExecuteStmt)) + if (IsA(utilityStmt, ExecuteStmt)) ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt, queryString, params, tstate); else if (IsA(utilityStmt, NotifyStmt)) @@ -247,6 +201,11 @@ ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt, * given a planned query, execute it if needed, and then print * EXPLAIN output * + * 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 + * not running the query. No cursor will be created, however. + * * This is exported because it's called back from prepare.c in the * EXPLAIN EXECUTE case * diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index eb381ebb70..939452650d 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.64 2007/04/16 01:14:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.65 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,34 +26,34 @@ #include "access/xact.h" #include "commands/portalcmds.h" #include "executor/executor.h" -#include "optimizer/planner.h" -#include "rewrite/rewriteHandler.h" #include "tcop/pquery.h" -#include "tcop/tcopprot.h" #include "utils/memutils.h" /* * PerformCursorOpen * Execute SQL DECLARE CURSOR command. + * + * The query has already been through parse analysis, rewriting, and planning. + * When it gets here, it looks like a SELECT PlannedStmt, except that the + * utilityStmt field is set. */ void -PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, +PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params, const char *queryString, bool isTopLevel) { - Oid *param_types; - int num_params; - List *rewritten; - Query *query; - PlannedStmt *plan; + DeclareCursorStmt *cstmt = (DeclareCursorStmt *) stmt->utilityStmt; Portal portal; MemoryContext oldContext; + if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt)) + elog(ERROR, "PerformCursorOpen called for non-cursor query"); + /* * Disallow empty-string cursor name (conflicts with protocol-level * unnamed portal). */ - if (!stmt->portalname || stmt->portalname[0] == '\0') + if (!cstmt->portalname || cstmt->portalname[0] == '\0') ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_NAME), errmsg("invalid cursor name: must not be empty"))); @@ -63,70 +63,24 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, * been executed inside a transaction block (or else, it would have no * user-visible effect). */ - if (!(stmt->options & CURSOR_OPT_HOLD)) + if (!(cstmt->options & CURSOR_OPT_HOLD)) RequireTransactionChain(isTopLevel, "DECLARE CURSOR"); - /* - * Don't allow both SCROLL and NO SCROLL to be specified - */ - if ((stmt->options & CURSOR_OPT_SCROLL) && - (stmt->options & CURSOR_OPT_NO_SCROLL)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("cannot specify both SCROLL and NO SCROLL"))); - - /* Convert parameter type data to the form parser wants */ - getParamListTypes(params, ¶m_types, &num_params); - - /* - * Run parse analysis and rewrite. Note this also acquires sufficient - * locks on the source table(s). - * - * Because the parser and planner tend to scribble on their input, we - * make a preliminary copy of the source querytree. This prevents - * problems in the case that the DECLARE CURSOR is in a portal or plpgsql - * function and is executed repeatedly. (See also the same hack in - * COPY and PREPARE.) XXX FIXME someday. - */ - rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), - queryString, param_types, num_params); - - /* We don't expect more or less than one result query */ - if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query)) - elog(ERROR, "unexpected rewrite result"); - query = (Query *) linitial(rewritten); - if (query->commandType != CMD_SELECT) - elog(ERROR, "unexpected rewrite result"); - - /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */ - if (query->into) - ereport(ERROR, - (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), - errmsg("DECLARE CURSOR cannot specify INTO"))); - - if (query->rowMarks != NIL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"), - errdetail("Cursors must be READ ONLY."))); - - /* plan the query */ - plan = planner(query, stmt->options, params); - /* * Create a portal and copy the plan into its memory context. */ - portal = CreatePortal(stmt->portalname, false, false); + portal = CreatePortal(cstmt->portalname, false, false); oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - plan = copyObject(plan); + stmt = copyObject(stmt); + stmt->utilityStmt = NULL; /* make it look like plain SELECT */ PortalDefineQuery(portal, NULL, queryString, "SELECT", /* cursor's query is always a SELECT */ - list_make1(plan), + list_make1(stmt), NULL); /*---------- @@ -150,10 +104,10 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, * based on whether it would require any additional runtime overhead to do * so. */ - portal->cursorOptions = stmt->options; + portal->cursorOptions = cstmt->options; if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL))) { - if (ExecSupportsBackwardScan(plan->planTree)) + if (ExecSupportsBackwardScan(stmt->planTree)) portal->cursorOptions |= CURSOR_OPT_SCROLL; else portal->cursorOptions |= CURSOR_OPT_NO_SCROLL; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index e9b953f709..997f66c818 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2007, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.74 2007/04/26 23:24:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.75 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,7 +57,6 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) int nargs; List *queries; Query *query; - const char *commandTag; List *query_list, *plan_list; int i; @@ -137,22 +136,15 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) switch (query->commandType) { case CMD_SELECT: - commandTag = "SELECT"; - break; case CMD_INSERT: - commandTag = "INSERT"; - break; case CMD_UPDATE: - commandTag = "UPDATE"; - break; case CMD_DELETE: - commandTag = "DELETE"; + /* OK */ break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), errmsg("utility statements cannot be prepared"))); - commandTag = NULL; /* keep compiler quiet */ break; } @@ -168,7 +160,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString) StorePreparedStatement(stmt->name, stmt->query, queryString, - commandTag, + CreateCommandTag((Node *) query), argtypes, nargs, 0, /* default cursor options */ @@ -244,11 +236,12 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString, errmsg("prepared statement is not a SELECT"))); pstmt = (PlannedStmt *) linitial(plan_list); if (!IsA(pstmt, PlannedStmt) || - pstmt->commandType != CMD_SELECT) + pstmt->commandType != CMD_SELECT || + pstmt->utilityStmt != NULL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); - pstmt->into = copyObject(stmt->into); + pstmt->intoClause = copyObject(stmt->into); MemoryContextSwitchTo(oldContext); @@ -689,7 +682,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt, if (execstmt->into) { - if (pstmt->commandType != CMD_SELECT) + if (pstmt->commandType != CMD_SELECT || + pstmt->utilityStmt != NULL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); @@ -697,7 +691,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt, /* Copy the stmt so we can modify it */ pstmt = copyObject(pstmt); - pstmt->into = execstmt->into; + pstmt->intoClause = execstmt->into; } /* diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index cacd7c6fe7..298d0c69c9 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.292 2007/03/29 00:15:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.293 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -420,7 +420,7 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) * * XXX should we allow this if the destination is temp? */ - if (plannedstmt->into != NULL) + if (plannedstmt->intoClause != NULL) goto fail; /* Fail if write permissions are requested on any non-temp table */ @@ -522,10 +522,10 @@ InitPlan(QueryDesc *queryDesc, int eflags) * correct tuple descriptors. (Other SELECT INTO stuff comes later.) */ estate->es_select_into = false; - if (operation == CMD_SELECT && plannedstmt->into != NULL) + if (operation == CMD_SELECT && plannedstmt->intoClause != NULL) { estate->es_select_into = true; - estate->es_into_oids = interpretOidsOption(plannedstmt->into->options); + estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options); } /* @@ -2395,7 +2395,7 @@ typedef struct static void OpenIntoRel(QueryDesc *queryDesc) { - IntoClause *into = queryDesc->plannedstmt->into; + IntoClause *into = queryDesc->plannedstmt->intoClause; EState *estate = queryDesc->estate; Relation intoRelationDesc; char *intoName; diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index b59228b0c9..9ad1391612 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.115 2007/04/16 01:14:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.116 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -317,7 +317,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */ /* Utility commands don't need Executor. */ - if (es->qd->operation != CMD_UTILITY) + if (es->qd->utilitystmt == NULL) { /* * Only set up to collect queued triggers if it's not a SELECT. @@ -346,9 +346,12 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) { ActiveSnapshot = es->qd->snapshot; - if (es->qd->operation == CMD_UTILITY) + if (es->qd->utilitystmt) { - ProcessUtility(es->qd->utilitystmt, + /* ProcessUtility needs the PlannedStmt for DECLARE CURSOR */ + ProcessUtility((es->qd->plannedstmt ? + (Node *) es->qd->plannedstmt : + es->qd->utilitystmt), fcache->src, es->qd->params, false, /* not top level */ @@ -366,7 +369,8 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) */ if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT && - es->qd->plannedstmt->into == NULL) + es->qd->plannedstmt->utilityStmt == NULL && + es->qd->plannedstmt->intoClause == NULL) count = 1L; else count = 0L; @@ -396,7 +400,7 @@ postquel_end(execution_state *es) es->status = F_EXEC_DONE; /* Utility commands don't need Executor. */ - if (es->qd->operation != CMD_UTILITY) + if (es->qd->utilitystmt == NULL) { /* Make our snapshot the active one for any called functions */ saveActiveSnapshot = ActiveSnapshot; @@ -894,7 +898,9 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, * Note: eventually replace this test with QueryReturnsTuples? We'd need * a more general method of determining the output type, though. */ - if (!(parse->commandType == CMD_SELECT && parse->into == NULL)) + if (!(parse->commandType == CMD_SELECT && + parse->utilityStmt == NULL && + parse->intoClause == NULL)) { if (rettype != VOIDOID) ereport(ERROR, diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 0a5625a0c5..f97a86fe21 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.178 2007/04/16 18:21:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.179 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1581,7 +1581,8 @@ _SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, ActiveSnapshot->curcid = GetCurrentCommandId(); } - if (IsA(stmt, PlannedStmt)) + if (IsA(stmt, PlannedStmt) && + ((PlannedStmt *) stmt)->utilityStmt == NULL) { qdesc = CreateQueryDesc((PlannedStmt *) stmt, ActiveSnapshot, @@ -1687,7 +1688,8 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount) switch (operation) { case CMD_SELECT: - if (queryDesc->plannedstmt->into) /* select into table? */ + Assert(queryDesc->plannedstmt->utilityStmt == NULL); + if (queryDesc->plannedstmt->intoClause) /* select into table? */ res = SPI_OK_SELINTO; else if (queryDesc->dest->mydest != DestSPI) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 988120a963..62853461a8 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.374 2007/04/26 16:13:10 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.375 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -77,7 +77,8 @@ _copyPlannedStmt(PlannedStmt *from) COPY_NODE_FIELD(planTree); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(resultRelations); - COPY_NODE_FIELD(into); + COPY_NODE_FIELD(utilityStmt); + COPY_NODE_FIELD(intoClause); COPY_NODE_FIELD(subplans); COPY_BITMAPSET_FIELD(rewindPlanIDs); COPY_NODE_FIELD(returningLists); @@ -1819,7 +1820,7 @@ _copyQuery(Query *from) COPY_SCALAR_FIELD(canSetTag); COPY_NODE_FIELD(utilityStmt); COPY_SCALAR_FIELD(resultRelation); - COPY_NODE_FIELD(into); + COPY_NODE_FIELD(intoClause); COPY_SCALAR_FIELD(hasAggs); COPY_SCALAR_FIELD(hasSubLinks); COPY_NODE_FIELD(rtable); @@ -1884,7 +1885,7 @@ _copySelectStmt(SelectStmt *from) SelectStmt *newnode = makeNode(SelectStmt); COPY_NODE_FIELD(distinctClause); - COPY_NODE_FIELD(into); + COPY_NODE_FIELD(intoClause); COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(fromClause); COPY_NODE_FIELD(whereClause); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index c1500948dc..99fb8f18b6 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.305 2007/04/26 16:13:11 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.306 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -722,7 +722,7 @@ _equalQuery(Query *a, Query *b) COMPARE_SCALAR_FIELD(canSetTag); COMPARE_NODE_FIELD(utilityStmt); COMPARE_SCALAR_FIELD(resultRelation); - COMPARE_NODE_FIELD(into); + COMPARE_NODE_FIELD(intoClause); COMPARE_SCALAR_FIELD(hasAggs); COMPARE_SCALAR_FIELD(hasSubLinks); COMPARE_NODE_FIELD(rtable); @@ -779,7 +779,7 @@ static bool _equalSelectStmt(SelectStmt *a, SelectStmt *b) { COMPARE_NODE_FIELD(distinctClause); - COMPARE_NODE_FIELD(into); + COMPARE_NODE_FIELD(intoClause); COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(fromClause); COMPARE_NODE_FIELD(whereClause); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 49202f8664..58d8ee620b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.305 2007/03/27 23:21:09 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.306 2007/04/27 22:05:47 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -244,7 +244,8 @@ _outPlannedStmt(StringInfo str, PlannedStmt *node) WRITE_NODE_FIELD(planTree); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(resultRelations); - WRITE_NODE_FIELD(into); + WRITE_NODE_FIELD(utilityStmt); + WRITE_NODE_FIELD(intoClause); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_NODE_FIELD(returningLists); @@ -1549,7 +1550,7 @@ _outSelectStmt(StringInfo str, SelectStmt *node) WRITE_NODE_TYPE("SELECT"); WRITE_NODE_FIELD(distinctClause); - WRITE_NODE_FIELD(into); + WRITE_NODE_FIELD(intoClause); WRITE_NODE_FIELD(targetList); WRITE_NODE_FIELD(fromClause); WRITE_NODE_FIELD(whereClause); @@ -1694,7 +1695,7 @@ _outQuery(StringInfo str, Query *node) appendStringInfo(str, " :utilityStmt <>"); WRITE_INT_FIELD(resultRelation); - WRITE_NODE_FIELD(into); + WRITE_NODE_FIELD(intoClause); WRITE_BOOL_FIELD(hasAggs); WRITE_BOOL_FIELD(hasSubLinks); WRITE_NODE_FIELD(rtable); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 1f3b81e275..be450e94c0 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.205 2007/03/27 23:21:09 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.206 2007/04/27 22:05:47 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -139,7 +139,7 @@ _readQuery(void) READ_BOOL_FIELD(canSetTag); READ_NODE_FIELD(utilityStmt); READ_INT_FIELD(resultRelation); - READ_NODE_FIELD(into); + READ_NODE_FIELD(intoClause); READ_BOOL_FIELD(hasAggs); READ_BOOL_FIELD(hasSubLinks); READ_NODE_FIELD(rtable); diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 92ade2fccf..16d71af183 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.31 2007/04/21 21:01:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.32 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -454,7 +454,8 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) subparse->commandType = CMD_SELECT; subparse->resultRelation = 0; subparse->returningList = NIL; - subparse->into = NULL; + subparse->utilityStmt = NULL; + subparse->intoClause = NULL; subparse->hasAggs = false; subparse->groupClause = NIL; subparse->havingQual = NULL; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5c65a78d6f..d5dc6f7c7e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.217 2007/04/16 01:14:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.218 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -90,6 +90,11 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams) ListCell *lp, *lr; + /* Cursor options may come from caller or from DECLARE CURSOR stmt */ + if (parse->utilityStmt && + IsA(parse->utilityStmt, DeclareCursorStmt)) + cursorOptions |= ((DeclareCursorStmt *) parse->utilityStmt)->options; + /* * Set up global state for this planner invocation. This data is needed * across all levels of sub-Query that might exist in the given command, @@ -156,7 +161,8 @@ planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->planTree = top_plan; result->rtable = glob->finalrtable; result->resultRelations = root->resultRelations; - result->into = parse->into; + result->utilityStmt = parse->utilityStmt; + result->intoClause = parse->intoClause; result->subplans = glob->subplans; result->rewindPlanIDs = glob->rewindPlanIDs; result->returningLists = root->returningLists; diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 1519c01e60..9ba293839c 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.47 2007/02/19 07:03:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.48 2007/04/27 22:05:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -627,7 +627,8 @@ is_simple_subquery(Query *subquery) */ if (!IsA(subquery, Query) || subquery->commandType != CMD_SELECT || - subquery->into != NULL) + subquery->utilityStmt != NULL || + subquery->intoClause != NULL) elog(ERROR, "subquery is bogus"); /* @@ -698,7 +699,8 @@ 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->into != NULL) + subquery->utilityStmt != NULL || + subquery->intoClause != NULL) elog(ERROR, "subquery is bogus"); /* Is it a set-operation query at all? */ diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index d4cb3f06b0..8b646516de 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.241 2007/04/02 03:49:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.242 2007/04/27 22:05:48 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2945,7 +2945,8 @@ inline_function(Oid funcid, Oid result_type, List *args, */ if (!IsA(querytree, Query) || querytree->commandType != CMD_SELECT || - querytree->into || + querytree->utilityStmt || + querytree->intoClause || querytree->hasAggs || querytree->hasSubLinks || querytree->rtable || diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a4e4418b14..68475387be 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -20,7 +20,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.362 2007/03/13 00:33:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.363 2007/04/27 22:05:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -118,6 +118,10 @@ static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); +static Query *transformDeclareCursorStmt(ParseState *pstate, + DeclareCursorStmt *stmt); +static Query *transformExplainStmt(ParseState *pstate, + ExplainStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt, List **extras_before, List **extras_after); static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, @@ -312,20 +316,6 @@ transformStmt(ParseState *pstate, Node *parseTree, switch (nodeTag(parseTree)) { - /* - * Non-optimizable statements - */ - case T_CreateStmt: - result = transformCreateStmt(pstate, (CreateStmt *) parseTree, - extras_before, extras_after); - break; - - case T_AlterTableStmt: - result = transformAlterTableStmt(pstate, - (AlterTableStmt *) parseTree, - extras_before, extras_after); - break; - /* * Optimizable statements */ @@ -355,6 +345,33 @@ transformStmt(ParseState *pstate, Node *parseTree, } break; + /* + * Non-optimizable statements + */ + case T_CreateStmt: + result = transformCreateStmt(pstate, (CreateStmt *) parseTree, + extras_before, extras_after); + break; + + case T_AlterTableStmt: + result = transformAlterTableStmt(pstate, + (AlterTableStmt *) parseTree, + extras_before, extras_after); + break; + + /* + * Special cases + */ + case T_DeclareCursorStmt: + result = transformDeclareCursorStmt(pstate, + (DeclareCursorStmt *) parseTree); + break; + + case T_ExplainStmt: + result = transformExplainStmt(pstate, + (ExplainStmt *) parseTree); + break; + default: /* @@ -546,9 +563,11 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, release_pstate_resources(sub_pstate); pfree(sub_pstate); + /* The grammar should have produced a SELECT, but it might have INTO */ Assert(IsA(selectQuery, Query)); Assert(selectQuery->commandType == CMD_SELECT); - if (selectQuery->into) + Assert(selectQuery->utilityStmt == NULL); + if (selectQuery->intoClause) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("INSERT ... SELECT cannot specify INTO"))); @@ -2029,6 +2048,8 @@ analyzeRuleStmt(RuleStmt *stmt, const char *queryString, /* * transformSelectStmt - * transforms a Select Statement + * + * Note: this is also used for DECLARE CURSOR statements. */ static Query * transformSelectStmt(ParseState *pstate, SelectStmt *stmt) @@ -2085,11 +2106,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) "LIMIT"); /* handle any SELECT INTO/CREATE TABLE AS spec */ - if (stmt->into) + if (stmt->intoClause) { - qry->into = stmt->into; - if (stmt->into->colNames) - applyColumnNames(qry->targetList, stmt->into->colNames); + qry->intoClause = stmt->intoClause; + if (stmt->intoClause->colNames) + applyColumnNames(qry->targetList, stmt->intoClause->colNames); } qry->rtable = pstate->p_rtable; @@ -2254,11 +2275,11 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"))); /* handle any CREATE TABLE AS spec */ - if (stmt->into) + if (stmt->intoClause) { - qry->into = stmt->into; - if (stmt->into->colNames) - applyColumnNames(qry->targetList, stmt->into->colNames); + qry->intoClause = stmt->intoClause; + if (stmt->intoClause->colNames) + applyColumnNames(qry->targetList, stmt->intoClause->colNames); } /* @@ -2345,14 +2366,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) leftmostSelect = leftmostSelect->larg; Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) && leftmostSelect->larg == NULL); - if (leftmostSelect->into) + if (leftmostSelect->intoClause) { - qry->into = leftmostSelect->into; - intoColNames = leftmostSelect->into->colNames; + qry->intoClause = leftmostSelect->intoClause; + intoColNames = leftmostSelect->intoClause->colNames; } /* clear this to prevent complaints in transformSetOperationTree() */ - leftmostSelect->into = NULL; + leftmostSelect->intoClause = NULL; /* * These are not one-time, exactly, but we want to process them here and @@ -2533,7 +2554,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt) /* * Validity-check both leaf and internal SELECTs for disallowed ops. */ - if (stmt->into) + if (stmt->intoClause) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"))); @@ -3113,6 +3134,105 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, } +/* + * transformDeclareCursorStmt - + * transform a DECLARE CURSOR Statement + * + * DECLARE CURSOR is a hybrid case: it's an optimizable statement (in fact not + * significantly different from a SELECT) as far as parsing/rewriting/planning + * are concerned, but it's not passed to the executor and so in that sense is + * a utility statement. We transform it into a Query exactly as if it were + * a SELECT, then stick the original DeclareCursorStmt into the utilityStmt + * field to carry the cursor name and options. + */ +static Query * +transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) +{ + Query *result; + List *extras_before = NIL, + *extras_after = NIL; + + /* + * Don't allow both SCROLL and NO SCROLL to be specified + */ + if ((stmt->options & CURSOR_OPT_SCROLL) && + (stmt->options & CURSOR_OPT_NO_SCROLL)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), + errmsg("cannot specify both SCROLL and NO SCROLL"))); + + result = transformStmt(pstate, stmt->query, + &extras_before, &extras_after); + + /* Shouldn't get any extras, since grammar only allows SelectStmt */ + if (extras_before || extras_after) + elog(ERROR, "unexpected extra stuff in cursor statement"); + if (!IsA(result, Query) || + result->commandType != CMD_SELECT || + result->utilityStmt != NULL) + elog(ERROR, "unexpected non-SELECT command in cursor statement"); + + /* 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"))); + + /* Implementation restriction (might go away someday) */ + if (result->rowMarks != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"), + errdetail("Cursors must be READ ONLY."))); + + /* We won't need the raw querytree any more */ + stmt->query = NULL; + + result->utilityStmt = (Node *) stmt; + + return result; +} + + +/* + * transformExplainStmt - + * transform an EXPLAIN Statement + * + * EXPLAIN is just like other utility statements in that we emit it as a + * CMD_UTILITY Query node with no transformation of the raw parse tree. + * However, if p_variableparams is set, it could be that the client is + * expecting us to resolve parameter types in something like + * EXPLAIN SELECT * FROM tab WHERE col = $1 + * To deal with such cases, we run parse analysis and throw away the result; + * this is a bit grotty but not worth contorting the rest of the system for. + * (The approach we use for DECLARE CURSOR won't work because the statement + * being explained isn't necessarily a SELECT, and in particular might rewrite + * to multiple parsetrees.) + */ +static Query * +transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) +{ + Query *result; + + if (pstate->p_variableparams) + { + List *extras_before = NIL, + *extras_after = NIL; + + /* Since parse analysis scribbles on its input, copy the tree first! */ + (void) transformStmt(pstate, copyObject(stmt->query), + &extras_before, &extras_after); + } + + /* Now return the untransformed command as a utility Query */ + result = makeNode(Query); + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + return result; +} + + /* exported so planner can check again after rewriting, query pullup, etc */ void CheckSelectLocking(Query *qry) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a69e6989a9..8884da2289 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.590 2007/04/26 16:13:11 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.591 2007/04/27 22:05:48 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2355,12 +2355,12 @@ CreateAsStmt: * be attached to that Select's target list. */ SelectStmt *n = findLeftmostSelect((SelectStmt *) $6); - if (n->into != NULL) + if (n->intoClause != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("CREATE TABLE AS cannot specify INTO"))); $4->rel->istemp = $2; - n->into = $4; + n->intoClause = $4; $$ = $6; } ; @@ -5993,7 +5993,7 @@ simple_select: SelectStmt *n = makeNode(SelectStmt); n->distinctClause = $2; n->targetList = $3; - n->into = $4; + n->intoClause = $4; n->fromClause = $5; n->whereClause = $6; n->groupClause = $7; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 0c718adc32..4c1fb0cc4c 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.164 2007/02/01 19:10:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.165 2007/04/27 22:05:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -447,9 +447,10 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) if (query == NULL || !IsA(query, Query)) elog(ERROR, "unexpected parse analysis result for subquery in FROM"); - if (query->commandType != CMD_SELECT) + if (query->commandType != CMD_SELECT || + query->utilityStmt != NULL) elog(ERROR, "expected SELECT query from subquery in FROM"); - if (query->into != NULL) + if (query->intoClause != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("subquery in FROM cannot have SELECT INTO"))); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index d9a6af8296..a0f92f66a6 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.216 2007/04/02 03:49:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.217 2007/04/27 22:05:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1117,7 +1117,8 @@ transformSubLink(ParseState *pstate, SubLink *sublink) elog(ERROR, "bad query in sub-select"); qtree = (Query *) linitial(qtrees); if (qtree->commandType != CMD_SELECT || - qtree->into != NULL) + qtree->utilityStmt != NULL || + qtree->intoClause != NULL) elog(ERROR, "bad query in sub-select"); sublink->subselect = (Node *) qtree; diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index ebb86f8bba..0263386d2a 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.88 2007/04/02 03:49:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.89 2007/04/27 22:05:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -519,7 +519,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod) if (stmt == NULL || !IsA(stmt, SelectStmt) || stmt->distinctClause != NIL || - stmt->into != NULL || + stmt->intoClause != NULL || stmt->fromClause != NIL || stmt->whereClause != NULL || stmt->groupClause != NIL || diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 864b00f1e7..e57e2ab046 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.119 2007/03/19 23:38:29 wieck Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.120 2007/04/27 22:05:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -294,7 +294,9 @@ DefineQueryRewrite(char *rulename, */ query = (Query *) linitial(action); if (!is_instead || - query->commandType != CMD_SELECT || query->into != NULL) + query->commandType != CMD_SELECT || + query->utilityStmt != NULL || + query->intoClause != NULL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("rules on SELECT must have action INSTEAD SELECT"))); diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index b54dea45dc..58ed351c59 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.115 2007/03/13 00:33:42 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.116 2007/04/27 22:05:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,7 +68,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->operation = plannedstmt->commandType; /* operation */ qd->plannedstmt = plannedstmt; /* plan */ - qd->utilitystmt = NULL; + qd->utilitystmt = plannedstmt->utilityStmt; /* in case DECLARE CURSOR */ qd->snapshot = snapshot; /* snapshot */ qd->crosscheck_snapshot = crosscheck_snapshot; /* RI check snapshot */ qd->dest = dest; /* output dest */ @@ -257,7 +257,8 @@ ChoosePortalStrategy(List *stmts) if (query->canSetTag) { if (query->commandType == CMD_SELECT && - query->into == NULL) + query->utilityStmt == NULL && + query->intoClause == NULL) return PORTAL_ONE_SELECT; if (query->commandType == CMD_UTILITY && query->utilityStmt != NULL) @@ -276,7 +277,8 @@ ChoosePortalStrategy(List *stmts) if (pstmt->canSetTag) { if (pstmt->commandType == CMD_SELECT && - pstmt->into == NULL) + pstmt->utilityStmt == NULL && + pstmt->intoClause == NULL) return PORTAL_ONE_SELECT; } } @@ -380,7 +382,8 @@ FetchStatementTargetList(Node *stmt) else { if (query->commandType == CMD_SELECT && - query->into == NULL) + query->utilityStmt == NULL && + query->intoClause == NULL) return query->targetList; if (query->returningList) return query->returningList; @@ -392,7 +395,8 @@ FetchStatementTargetList(Node *stmt) PlannedStmt *pstmt = (PlannedStmt *) stmt; if (pstmt->commandType == CMD_SELECT && - pstmt->into == NULL) + pstmt->utilityStmt == NULL && + pstmt->intoClause == NULL) return pstmt->planTree->targetlist; if (pstmt->returningLists) return (List *) linitial(pstmt->returningLists); @@ -1222,7 +1226,8 @@ PortalRunMulti(Portal portal, bool isTopLevel, */ CHECK_FOR_INTERRUPTS(); - if (IsA(stmt, PlannedStmt)) + if (IsA(stmt, PlannedStmt) && + ((PlannedStmt *) stmt)->utilityStmt == NULL) { /* * process a plannable query. diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 9dd700ffe7..028cb47c7a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.278 2007/04/26 16:13:12 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.279 2007/04/27 22:05:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -268,7 +268,7 @@ CommandIsReadOnly(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->into != NULL) + if (stmt->intoClause != NULL) return false; /* SELECT INTO */ else if (stmt->rowMarks != NIL) return false; /* SELECT FOR UPDATE/SHARE */ @@ -505,10 +505,20 @@ ProcessUtility(Node *parsetree, /* * Portal (cursor) manipulation + * + * Note: DECLARE CURSOR is processed mostly as a SELECT, and + * therefore what we will get here is a PlannedStmt not a bare + * DeclareCursorStmt. */ - case T_DeclareCursorStmt: - PerformCursorOpen((DeclareCursorStmt *) parsetree, params, - queryString, isTopLevel); + case T_PlannedStmt: + { + PlannedStmt *stmt = (PlannedStmt *) parsetree; + + if (stmt->utilityStmt == NULL || + !IsA(stmt->utilityStmt, DeclareCursorStmt)) + elog(ERROR, "non-DECLARE CURSOR PlannedStmt passed to ProcessUtility"); + PerformCursorOpen(stmt, params, queryString, isTopLevel); + } break; case T_ClosePortalStmt: @@ -1272,8 +1282,9 @@ QueryReturnsTuples(Query *parsetree) switch (parsetree->commandType) { case CMD_SELECT: - /* returns tuples ... unless it's SELECT INTO */ - if (parsetree->into == NULL) + /* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */ + if (parsetree->utilityStmt == NULL && + parsetree->intoClause == NULL) return true; break; case CMD_INSERT: @@ -1899,7 +1910,12 @@ CreateCommandTag(Node *parsetree) * will be useful for complaints about read-only * statements */ - if (stmt->into != NULL) + if (stmt->utilityStmt != NULL) + { + Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); + tag = "DECLARE CURSOR"; + } + else if (stmt->intoClause != NULL) tag = "SELECT INTO"; else if (stmt->rowMarks != NIL) { @@ -1942,7 +1958,12 @@ CreateCommandTag(Node *parsetree) * will be useful for complaints about read-only * statements */ - if (stmt->into != NULL) + if (stmt->utilityStmt != NULL) + { + Assert(IsA(stmt->utilityStmt, DeclareCursorStmt)); + tag = "DECLARE CURSOR"; + } + else if (stmt->intoClause != NULL) tag = "SELECT INTO"; else if (stmt->rowMarks != NIL) { @@ -2009,7 +2030,7 @@ GetCommandLogLevel(Node *parsetree) break; case T_SelectStmt: - if (((SelectStmt *) parsetree)->into) + if (((SelectStmt *) parsetree)->intoClause) lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ else lev = LOGSTMT_ALL; @@ -2330,10 +2351,10 @@ GetCommandLogLevel(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->into != NULL) + if (stmt->intoClause != NULL) lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ else - lev = LOGSTMT_ALL; + lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */ break; case CMD_UPDATE: @@ -2359,10 +2380,10 @@ GetCommandLogLevel(Node *parsetree) switch (stmt->commandType) { case CMD_SELECT: - if (stmt->into != NULL) + if (stmt->intoClause != NULL) lev = LOGSTMT_DDL; /* CREATE AS, SELECT INTO */ else - lev = LOGSTMT_ALL; + lev = LOGSTMT_ALL; /* SELECT or DECLARE CURSOR */ break; case CMD_UPDATE: diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index 3d77404613..66a2effc50 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.22 2007/03/13 00:33:43 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.23 2007/04/27 22:05:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,10 +15,11 @@ #define PORTALCMDS_H #include "nodes/parsenodes.h" +#include "nodes/plannodes.h" #include "utils/portal.h" -extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, +extern void PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params, const char *queryString, bool isTopLevel); extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 09cb4ff503..179d7a765d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.347 2007/04/26 16:13:14 neilc Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.348 2007/04/27 22:05:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,9 +82,11 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ * * Utility statements (i.e. non-optimizable statements) have the * utilityStmt field set, and the Query itself is mostly dummy. + * DECLARE CURSOR is a special case: it is represented like a SELECT, + * but the original DeclareCursorStmt is stored in utilityStmt. * * Planning converts a Query tree into a Plan tree headed by a PlannedStmt - * noded --- the Query structure is not used by the executor. + * node --- the Query structure is not used by the executor. */ typedef struct Query { @@ -96,13 +98,13 @@ typedef struct Query bool canSetTag; /* do I set the command result tag? */ - Node *utilityStmt; /* non-null if this is a non-optimizable - * statement */ + Node *utilityStmt; /* non-null if this is DECLARE CURSOR or a + * non-optimizable statement */ int resultRelation; /* rtable index of target relation for * INSERT/UPDATE/DELETE; 0 for SELECT */ - IntoClause *into; /* target for SELECT INTO / CREATE TABLE AS */ + IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */ bool hasAggs; /* has aggregates in tlist or havingQual */ bool hasSubLinks; /* has subquery SubLink */ @@ -732,7 +734,7 @@ typedef struct SelectStmt */ List *distinctClause; /* NULL, list of DISTINCT ON exprs, or * lcons(NIL,NIL) for all (SELECT DISTINCT) */ - IntoClause *into; /* target for SELECT INTO / CREATE TABLE AS */ + IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */ List *targetList; /* the target list (of ResTarget) */ List *fromClause; /* the FROM clause */ Node *whereClause; /* WHERE qualification */ @@ -1427,6 +1429,10 @@ typedef struct CommentStmt /* ---------------------- * Declare Cursor Statement + * + * Note: the "query" field of DeclareCursorStmt is only used in the raw grammar + * output. After parse analysis it's set to null, and the Query points to the + * DeclareCursorStmt, not vice versa. * ---------------------- */ #define CURSOR_OPT_BINARY 0x0001 /* BINARY */ @@ -1441,7 +1447,7 @@ typedef struct DeclareCursorStmt NodeTag type; char *portalname; /* name of the portal (cursor) */ int options; /* bitmask of options (see above) */ - Node *query; /* the SELECT query */ + Node *query; /* the raw SELECT query */ } DeclareCursorStmt; /* ---------------------- diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index aecfb51939..9d336e4b88 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.93 2007/02/27 01:11:26 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.94 2007/04/27 22:05:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,7 +46,9 @@ typedef struct PlannedStmt /* rtable indexes of target relations for INSERT/UPDATE/DELETE */ List *resultRelations; /* integer list of RT indexes, or NIL */ - IntoClause *into; /* target for SELECT INTO / CREATE TABLE AS */ + 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 */