提交 3f8db37c 编写于 作者: T Tom Lane

Tweak SPI_cursor_open to allow INSERT/UPDATE/DELETE RETURNING; this was

merely a matter of fixing the error check, since the underlying Portal
infrastructure already handles it.  This in turn allows these statements
to be used in some existing plpgsql and plperl contexts, such as a
plpgsql FOR loop.  Also, do some marginal code cleanup in places that
were being sloppy about distinguishing SELECT from SELECT INTO.
上级 883f4b42
<!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.55 2006/05/30 11:40:21 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.56 2006/08/12 20:05:54 tgl Exp $ -->
<chapter id="plperl"> <chapter id="plperl">
<title>PL/Perl - Perl Procedural Language</title> <title>PL/Perl - Perl Procedural Language</title>
...@@ -244,18 +244,8 @@ $$ LANGUAGE plperl; ...@@ -244,18 +244,8 @@ $$ LANGUAGE plperl;
SELECT * FROM perl_set(); SELECT * FROM perl_set();
</programlisting> </programlisting>
</para> </para>
<para>
<application>PL/Perl</> does not currently have full support for
domain types: it treats a domain the same as the underlying scalar
type. This means that constraints associated with the domain will
not be enforced. This is not an issue for function arguments, but
it is a hazard if you declare a <application>PL/Perl</> function
as returning a domain type.
</para>
<para> <para>
If you wish to use the <literal>strict</> pragma with your code, If you wish to use the <literal>strict</> pragma with your code,
the easiest way to do so is to <command>SET</> the easiest way to do so is to <command>SET</>
...@@ -439,26 +429,26 @@ SELECT * from lotsa_md5(500); ...@@ -439,26 +429,26 @@ SELECT * from lotsa_md5(500);
<para> <para>
The advantage of prepared queries is that is it possible to use one prepared plan for more The advantage of prepared queries is that is it possible to use one prepared plan for more
than one query execution. After the plan is not needed anymore, it must be freed with than one query execution. After the plan is not needed anymore, it may be freed with
<literal>spi_freeplan</literal>: <literal>spi_freeplan</literal>:
</para> </para>
<para> <para>
<programlisting> <programlisting>
CREATE OR REPLACE FUNCTION init() RETURNS INTEGER AS $$ CREATE OR REPLACE FUNCTION init() RETURNS INTEGER AS $$
$_SHARED{my_plan} = spi_prepare( 'SELECT (now() + $1)::date AS now', 'INTERVAL'); $_SHARED{my_plan} = spi_prepare( 'SELECT (now() + $1)::date AS now', 'INTERVAL');
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$ CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$
return spi_exec_prepared( return spi_exec_prepared(
$_SHARED{my_plan}, $_SHARED{my_plan},
$_[0], $_[0],
)->{rows}->[0]->{now}; )->{rows}->[0]->{now};
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
CREATE OR REPLACE FUNCTION done() RETURNS INTEGER AS $$ CREATE OR REPLACE FUNCTION done() RETURNS INTEGER AS $$
spi_freeplan( $_SHARED{my_plan}); spi_freeplan( $_SHARED{my_plan});
undef $_SHARED{my_plan}; undef $_SHARED{my_plan};
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
SELECT init(); SELECT init();
...@@ -478,16 +468,14 @@ SELECT done(); ...@@ -478,16 +468,14 @@ SELECT done();
</para> </para>
<para> <para>
<literal>spi_cursor_close</literal> can be used to abort sequence of Normally, <function>spi_fetchrow</> should be repeated until it
<literal>spi_fetchrow</literal> calls. Normally, the call to returns <literal>undef</literal>, indicating that there are no more
<literal>spi_fetchrow</literal> that returns <literal>undef</literal> is rows to read. The cursor is automatically freed when
the signal that there are no more rows to read. Also <function>spi_fetchrow</> returns <literal>undef</literal>.
that call automatically frees the cursor associated with the query. If it is desired not If you do not wish to read all the rows, instead call
to read all retuned rows, <literal>spi_cursor_close</literal> must be <function>spi_cursor_close</> to free the cursor.
called to avoid memory leaks. Failure to do so will result in memory leaks.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -630,8 +618,8 @@ CREATE FUNCTION badfunc() RETURNS integer AS $$ ...@@ -630,8 +618,8 @@ CREATE FUNCTION badfunc() RETURNS integer AS $$
return 1; return 1;
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
</programlisting> </programlisting>
The creation of this function will fail as its use of a forbidden The creation of this function will fail as its use of a forbidden
operation will be be caught by the validator. operation will be be caught by the validator.
</para> </para>
<para> <para>
...@@ -748,8 +736,8 @@ $$ LANGUAGE plperl; ...@@ -748,8 +736,8 @@ $$ LANGUAGE plperl;
<listitem> <listitem>
<para> <para>
Name of the table on which the trigger fired. This has been deprecated, Name of the table on which the trigger fired. This has been deprecated,
and could be removed in a future release. and could be removed in a future release.
Please use $_TD-&gt;{table_name} instead. Please use $_TD-&gt;{table_name} instead.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.97 2006/06/16 23:29:26 tgl Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.98 2006/08/12 20:05:54 tgl Exp $ -->
<chapter id="plpgsql"> <chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title> <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
...@@ -2040,9 +2040,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>; ...@@ -2040,9 +2040,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
The <replaceable>target</replaceable> is a record variable, row variable, The <replaceable>target</replaceable> is a record variable, row variable,
or comma-separated list of scalar variables. or comma-separated list of scalar variables.
The <replaceable>target</replaceable> is successively assigned each row The <replaceable>target</replaceable> is successively assigned each row
resulting from the <replaceable>query</replaceable> (which must be a resulting from the <replaceable>query</replaceable> and the loop body is
<command>SELECT</command> command) and the loop body is executed for each executed for each row. Here is an example:
row. Here is an example:
<programlisting> <programlisting>
CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$ CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE DECLARE
...@@ -2069,6 +2068,15 @@ $$ LANGUAGE plpgsql; ...@@ -2069,6 +2068,15 @@ $$ LANGUAGE plpgsql;
assigned row value is still accessible after the loop. assigned row value is still accessible after the loop.
</para> </para>
<para>
The <replaceable>query</replaceable> used in this type of <literal>FOR</>
statement can be any query that returns rows to the caller:
<command>SELECT</> (without <literal>INTO</>) is the most common case,
but you can also use <command>INSERT</>, <command>UPDATE</>, or
<command>DELETE</> with a <literal>RETURNING</> clause. Some utility
commands such as <command>EXPLAIN</> will work too.
</para>
<para> <para>
The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
rows: rows:
...@@ -2078,12 +2086,11 @@ FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</r ...@@ -2078,12 +2086,11 @@ FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</r
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP <optional> <replaceable>label</replaceable> </optional>; END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
This is like the previous form, except that the source This is like the previous form, except that the source query
<command>SELECT</command> statement is specified as a string is specified as a string expression, which is evaluated and replanned
expression, which is evaluated and replanned on each entry to on each entry to the <literal>FOR</> loop. This allows the programmer to
the <literal>FOR</> loop. This allows the programmer to choose the speed of choose the speed of a preplanned query or the flexibility of a dynamic
a preplanned query or the flexibility of a dynamic query, just query, just as with a plain <command>EXECUTE</command> statement.
as with a plain <command>EXECUTE</command> statement.
</para> </para>
<note> <note>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.45 2006/03/10 19:10:49 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.46 2006/08/12 20:05:54 tgl Exp $ -->
<chapter id="spi"> <chapter id="spi">
<title>Server Programming Interface</title> <title>Server Programming Interface</title>
...@@ -535,15 +535,15 @@ typedef struct ...@@ -535,15 +535,15 @@ typedef struct
<term><symbol>SPI_ERROR_TRANSACTION</symbol></term> <term><symbol>SPI_ERROR_TRANSACTION</symbol></term>
<listitem> <listitem>
<para> <para>
if any command involving transaction manipulation was attempted if any command involving transaction manipulation was attempted
(<command>BEGIN</>, (<command>BEGIN</>,
<command>COMMIT</>, <command>COMMIT</>,
<command>ROLLBACK</>, <command>ROLLBACK</>,
<command>SAVEPOINT</>, <command>SAVEPOINT</>,
<command>PREPARE TRANSACTION</>, <command>PREPARE TRANSACTION</>,
<command>COMMIT PREPARED</>, <command>COMMIT PREPARED</>,
<command>ROLLBACK PREPARED</>, <command>ROLLBACK PREPARED</>,
or any variant thereof) or any variant thereof)
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -917,10 +917,12 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>) ...@@ -917,10 +917,12 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
<para> <para>
<function>SPI_is_cursor_plan</function> returns <symbol>true</symbol> <function>SPI_is_cursor_plan</function> returns <symbol>true</symbol>
if a plan prepared by <function>SPI_prepare</function> can be passed if a plan prepared by <function>SPI_prepare</function> can be passed
as an argument to <function>SPI_cursor_open</function> and <symbol> as an argument to <function>SPI_cursor_open</function>, or
false</symbol> if that is not the case. The criteria are that the <symbol>false</symbol> if that is not the case. The criteria are that the
<parameter>plan</parameter> represents one single command and that this <parameter>plan</parameter> represents one single command and that this
command is a <command>SELECT</command> without an <command>INTO</command> command returns tuples to the caller; for example, <command>SELECT</>
is allowed unless it contains an <literal>INTO</> clause, and
<command>UPDATE</> is allowed only if it contains a <literal>RETURNING</>
clause. clause.
</para> </para>
</refsect1> </refsect1>
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.49 2006/08/08 01:23:15 momjian Exp $ * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.50 2006/08/12 20:05:54 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -87,7 +87,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params) ...@@ -87,7 +87,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
if (query->into) if (query->into)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR may not specify INTO"))); errmsg("DECLARE CURSOR may not specify INTO")));
if (query->rowMarks != NIL) if (query->rowMarks != NIL)
ereport(ERROR, ereport(ERROR,
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.278 2006/08/12 02:52:04 tgl Exp $ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.279 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -497,7 +497,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) ...@@ -497,7 +497,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/* /*
* if there is a result relation, initialize result relation stuff * if there is a result relation, initialize result relation stuff
*/ */
if (parseTree->resultRelation != 0 && operation != CMD_SELECT) if (parseTree->resultRelation)
{ {
List *resultRelations = parseTree->resultRelations; List *resultRelations = parseTree->resultRelations;
int numResultRelations; int numResultRelations;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.104 2006/07/14 14:52:19 momjian Exp $ * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.105 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -361,7 +361,9 @@ postquel_getnext(execution_state *es) ...@@ -361,7 +361,9 @@ postquel_getnext(execution_state *es)
* run it to completion. (If we run to completion then * run it to completion. (If we run to completion then
* ExecutorRun is guaranteed to return NULL.) * ExecutorRun is guaranteed to return NULL.)
*/ */
if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT) if (LAST_POSTQUEL_COMMAND(es) &&
es->qd->operation == CMD_SELECT &&
es->qd->parsetree->into == NULL)
count = 1L; count = 1L;
else else
count = 0L; count = 0L;
...@@ -868,7 +870,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -868,7 +870,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
JunkFilter **junkFilter) JunkFilter **junkFilter)
{ {
Query *parse; Query *parse;
int cmd; bool isSelect;
List *tlist; List *tlist;
ListCell *tlistitem; ListCell *tlistitem;
int tlistlen; int tlistlen;
...@@ -893,15 +895,18 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -893,15 +895,18 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
/* find the final query */ /* find the final query */
parse = (Query *) lfirst(list_tail(queryTreeList)); parse = (Query *) lfirst(list_tail(queryTreeList));
cmd = parse->commandType; /*
tlist = parse->targetList; * Note: eventually replace this with QueryReturnsTuples? We'd need
* a more general method of determining the output type, though.
*/
isSelect = (parse->commandType == CMD_SELECT && parse->into == NULL);
/* /*
* The last query must be a SELECT if and only if return type isn't VOID. * The last query must be a SELECT if and only if return type isn't VOID.
*/ */
if (rettype == VOIDOID) if (rettype == VOIDOID)
{ {
if (cmd == CMD_SELECT) if (isSelect)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s", errmsg("return type mismatch in function declared to return %s",
...@@ -911,7 +916,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -911,7 +916,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
} }
/* by here, the function is declared to return some type */ /* by here, the function is declared to return some type */
if (cmd != CMD_SELECT) if (!isSelect)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s", errmsg("return type mismatch in function declared to return %s",
...@@ -921,6 +926,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, ...@@ -921,6 +926,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
/* /*
* Count the non-junk entries in the result targetlist. * Count the non-junk entries in the result targetlist.
*/ */
tlist = parse->targetList;
tlistlen = ExecCleanTargetListLength(tlist); tlistlen = ExecCleanTargetListLength(tlist);
fn_typtype = get_typtype(rettype); fn_typtype = get_typtype(rettype);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.154 2006/08/12 02:52:04 tgl Exp $ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.155 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -837,26 +837,12 @@ SPI_cursor_open(const char *name, void *plan, ...@@ -837,26 +837,12 @@ SPI_cursor_open(const char *name, void *plan,
planTree = (Plan *) linitial(ptlist); planTree = (Plan *) linitial(ptlist);
/* Must be a query that returns tuples */ /* Must be a query that returns tuples */
switch (queryTree->commandType) if (!QueryReturnsTuples(queryTree))
{ ereport(ERROR,
case CMD_SELECT: (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
if (queryTree->into != NULL) /* translator: %s is name of a SQL command, eg INSERT */
ereport(ERROR, errmsg("cannot open %s query as cursor",
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION), CreateQueryTag(queryTree))));
errmsg("cannot open SELECT INTO query as cursor")));
break;
case CMD_UTILITY:
if (!UtilityReturnsTuples(queryTree->utilityStmt))
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open non-SELECT query as cursor")));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("cannot open non-SELECT query as cursor")));
break;
}
/* Reset SPI result (note we deliberately don't touch lastoid) */ /* Reset SPI result (note we deliberately don't touch lastoid) */
SPI_processed = 0; SPI_processed = 0;
...@@ -876,7 +862,7 @@ SPI_cursor_open(const char *name, void *plan, ...@@ -876,7 +862,7 @@ SPI_cursor_open(const char *name, void *plan,
portal = CreatePortal(name, false, false); portal = CreatePortal(name, false, false);
} }
/* Switch to portals memory and copy the parsetree and plan to there */ /* Switch to portal's memory and copy the parsetree and plan to there */
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
queryTree = copyObject(queryTree); queryTree = copyObject(queryTree);
planTree = copyObject(planTree); planTree = copyObject(planTree);
...@@ -919,9 +905,9 @@ SPI_cursor_open(const char *name, void *plan, ...@@ -919,9 +905,9 @@ SPI_cursor_open(const char *name, void *plan,
* Set up the portal. * Set up the portal.
*/ */
PortalDefineQuery(portal, PortalDefineQuery(portal,
NULL, NULL, /* no statement name */
spiplan->query, spiplan->query,
"SELECT", /* don't have the raw parse tree... */ CreateQueryTag(queryTree),
list_make1(queryTree), list_make1(queryTree),
list_make1(planTree), list_make1(planTree),
PortalGetHeapMemory(portal)); PortalGetHeapMemory(portal));
...@@ -954,9 +940,16 @@ SPI_cursor_open(const char *name, void *plan, ...@@ -954,9 +940,16 @@ SPI_cursor_open(const char *name, void *plan,
*/ */
PortalStart(portal, paramLI, snapshot); PortalStart(portal, paramLI, snapshot);
Assert(portal->strategy == PORTAL_ONE_SELECT || /*
portal->strategy == PORTAL_ONE_RETURNING || * If this test fails then we're out of sync with pquery.c about
portal->strategy == PORTAL_UTIL_SELECT); * which queries can return tuples...
*/
if (portal->strategy == PORTAL_MULTI_QUERY)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
/* translator: %s is name of a SQL command, eg INSERT */
errmsg("cannot open %s query as cursor",
CreateQueryTag(queryTree))));
/* Return the created portal */ /* Return the created portal */
return portal; return portal;
...@@ -1046,12 +1039,12 @@ SPI_getargcount(void *plan) ...@@ -1046,12 +1039,12 @@ SPI_getargcount(void *plan)
/* /*
* Returns true if the plan contains exactly one command * Returns true if the plan contains exactly one command
* and that command originates from normal SELECT (i.e. * and that command returns tuples to the caller (eg, SELECT or
* *not* a SELECT ... INTO). In essence, the result indicates * INSERT ... RETURNING, but not SELECT ... INTO). In essence,
* if the command can be used with SPI_cursor_open * the result indicates if the command can be used with SPI_cursor_open
* *
* Parameters * Parameters
* plan A plan previously prepared using SPI_prepare * plan: A plan previously prepared using SPI_prepare
*/ */
bool bool
SPI_is_cursor_plan(void *plan) SPI_is_cursor_plan(void *plan)
...@@ -1070,7 +1063,7 @@ SPI_is_cursor_plan(void *plan) ...@@ -1070,7 +1063,7 @@ SPI_is_cursor_plan(void *plan)
{ {
Query *queryTree = (Query *) linitial((List *) linitial(qtlist)); Query *queryTree = (Query *) linitial((List *) linitial(qtlist));
if (queryTree->commandType == CMD_SELECT && queryTree->into == NULL) if (QueryReturnsTuples(queryTree))
return true; return true;
} }
return false; return false;
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.41 2006/08/12 02:52:05 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.42 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -623,7 +623,6 @@ is_simple_subquery(Query *subquery) ...@@ -623,7 +623,6 @@ is_simple_subquery(Query *subquery)
*/ */
if (!IsA(subquery, Query) || if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT || subquery->commandType != CMD_SELECT ||
subquery->resultRelation != 0 ||
subquery->into != NULL) subquery->into != NULL)
elog(ERROR, "subquery is bogus"); elog(ERROR, "subquery is bogus");
...@@ -686,7 +685,6 @@ is_simple_union_all(Query *subquery) ...@@ -686,7 +685,6 @@ is_simple_union_all(Query *subquery)
/* Let's just make sure it's a valid subselect ... */ /* Let's just make sure it's a valid subselect ... */
if (!IsA(subquery, Query) || if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT || subquery->commandType != CMD_SELECT ||
subquery->resultRelation != 0 ||
subquery->into != NULL) subquery->into != NULL)
elog(ERROR, "subquery is bogus"); elog(ERROR, "subquery is bogus");
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.218 2006/08/12 02:52:05 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.219 2006/08/12 20:05:55 tgl Exp $
* *
* HISTORY * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -2689,7 +2689,6 @@ inline_function(Oid funcid, Oid result_type, List *args, ...@@ -2689,7 +2689,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
*/ */
if (!IsA(querytree, Query) || if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT || querytree->commandType != CMD_SELECT ||
querytree->resultRelation != 0 ||
querytree->into || querytree->into ||
querytree->hasAggs || querytree->hasAggs ||
querytree->hasSubLinks || querytree->hasSubLinks ||
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.345 2006/08/12 02:52:05 tgl Exp $ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.346 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -3116,6 +3116,15 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) ...@@ -3116,6 +3116,15 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
/* Shouldn't get any extras, since grammar only allows SelectStmt */ /* Shouldn't get any extras, since grammar only allows SelectStmt */
if (extras_before || extras_after) if (extras_before || extras_after)
elog(ERROR, "unexpected extra stuff in cursor statement"); elog(ERROR, "unexpected extra stuff in cursor statement");
if (!IsA(stmt->query, Query) ||
((Query *) stmt->query)->commandType != CMD_SELECT)
elog(ERROR, "unexpected non-SELECT command in cursor statement");
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (((Query *) stmt->query)->into)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR may not specify INTO")));
return result; return result;
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.155 2006/07/26 19:31:51 tgl Exp $ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.156 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -447,7 +447,7 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r) ...@@ -447,7 +447,7 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
if (query->commandType != CMD_SELECT) if (query->commandType != CMD_SELECT)
elog(ERROR, "expected SELECT query from subquery in FROM"); elog(ERROR, "expected SELECT query from subquery in FROM");
if (query->resultRelation != 0 || query->into != NULL) if (query->into != NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery in FROM may not have SELECT INTO"))); errmsg("subquery in FROM may not have SELECT INTO")));
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.196 2006/08/02 01:59:46 joe Exp $ * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.197 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1104,7 +1104,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) ...@@ -1104,7 +1104,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
elog(ERROR, "bad query in sub-select"); elog(ERROR, "bad query in sub-select");
qtree = (Query *) linitial(qtrees); qtree = (Query *) linitial(qtrees);
if (qtree->commandType != CMD_SELECT || if (qtree->commandType != CMD_SELECT ||
qtree->resultRelation != 0) qtree->into != NULL)
elog(ERROR, "bad query in sub-select"); elog(ERROR, "bad query in sub-select");
sublink->subselect = (Node *) qtree; sublink->subselect = (Node *) qtree;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.111 2006/07/18 17:42:00 momjian Exp $ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.112 2006/08/12 20:05:55 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -264,7 +264,8 @@ DefineQueryRewrite(RuleStmt *stmt) ...@@ -264,7 +264,8 @@ DefineQueryRewrite(RuleStmt *stmt)
* ... the one action must be a SELECT, ... * ... the one action must be a SELECT, ...
*/ */
query = (Query *) linitial(action); query = (Query *) linitial(action);
if (!is_instead || query->commandType != CMD_SELECT) if (!is_instead ||
query->commandType != CMD_SELECT || query->into != NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rules on SELECT must have action INSTEAD SELECT"))); errmsg("rules on SELECT must have action INSTEAD SELECT")));
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.264 2006/08/12 02:52:05 tgl Exp $ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.265 2006/08/12 20:05:56 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1224,6 +1224,38 @@ UtilityTupleDescriptor(Node *parsetree) ...@@ -1224,6 +1224,38 @@ UtilityTupleDescriptor(Node *parsetree)
} }
/*
* QueryReturnsTuples
* Return "true" if this Query will send output to the destination.
*/
bool
QueryReturnsTuples(Query *parsetree)
{
switch (parsetree->commandType)
{
case CMD_SELECT:
/* returns tuples ... unless it's SELECT INTO */
if (parsetree->into == NULL)
return true;
break;
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
/* the forms with RETURNING return tuples */
if (parsetree->returningList)
return true;
break;
case CMD_UTILITY:
return UtilityReturnsTuples(parsetree->utilityStmt);
case CMD_UNKNOWN:
case CMD_NOTHING:
/* probably shouldn't get here */
break;
}
return false; /* default */
}
/* /*
* CreateCommandTag * CreateCommandTag
* utility to get a string representation of the * utility to get a string representation of the
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.322 2006/08/12 02:52:06 tgl Exp $ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.323 2006/08/12 20:05:56 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -89,7 +89,8 @@ typedef struct Query ...@@ -89,7 +89,8 @@ typedef struct Query
Node *utilityStmt; /* non-null if this is a non-optimizable Node *utilityStmt; /* non-null if this is a non-optimizable
* statement */ * statement */
int resultRelation; /* target relation (index into rtable) */ int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */
RangeVar *into; /* target relation for SELECT INTO */ RangeVar *into; /* target relation for SELECT INTO */
List *intoOptions; /* options from WITH clause */ List *intoOptions; /* options from WITH clause */
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.27 2006/03/05 15:59:00 momjian Exp $ * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.28 2006/08/12 20:05:56 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -28,6 +28,8 @@ extern const char *CreateCommandTag(Node *parsetree); ...@@ -28,6 +28,8 @@ extern const char *CreateCommandTag(Node *parsetree);
extern const char *CreateQueryTag(Query *parsetree); extern const char *CreateQueryTag(Query *parsetree);
extern bool QueryReturnsTuples(Query *parsetree);
extern bool QueryIsReadOnly(Query *parsetree); extern bool QueryIsReadOnly(Query *parsetree);
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs); extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册