Tweak SPI_cursor_open to allow INSERT/UPDATE/DELETE RETURNING; this was
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 12 Aug 2006 20:05:56 +0000 (20:05 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 12 Aug 2006 20:05:56 +0000 (20:05 +0000)
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.

16 files changed:
doc/src/sgml/plperl.sgml
doc/src/sgml/plpgsql.sgml
doc/src/sgml/spi.sgml
src/backend/commands/portalcmds.c
src/backend/executor/execMain.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/optimizer/prep/prepjointree.c
src/backend/optimizer/util/clauses.c
src/backend/parser/analyze.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_expr.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/utility.c
src/include/nodes/parsenodes.h
src/include/tcop/utility.h

index 12ac0157c3ee37a79ea5e4631a7cfa60400c28cd..32a7b12c710f66276fbb0d0d6b3d907a6e8354a8 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $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">
   <title>PL/Perl - Perl Procedural Language</title>
@@ -244,18 +244,8 @@ $$ LANGUAGE plperl;
 
 SELECT * FROM perl_set();
 </programlisting>
-
   </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>
    If you wish to use the <literal>strict</> pragma with your code,
    the easiest way to do so is to <command>SET</>
@@ -439,26 +429,26 @@ SELECT * from lotsa_md5(500);
     
     <para>
     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>:
     </para>
 
     <para>
     <programlisting>
 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;
 
 CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$
-       return spi_exec_prepared( 
-               $_SHARED{my_plan},
-               $_[0],
-       )->{rows}->[0]->{now};
+        return spi_exec_prepared( 
+                $_SHARED{my_plan},
+                $_[0],
+        )->{rows}->[0]->{now};
 $$ LANGUAGE plperl;
 
 CREATE OR REPLACE FUNCTION done() RETURNS INTEGER AS $$
-       spi_freeplan( $_SHARED{my_plan});
-       undef $_SHARED{my_plan};
+        spi_freeplan( $_SHARED{my_plan});
+        undef $_SHARED{my_plan};
 $$ LANGUAGE plperl;
 
 SELECT init();
@@ -478,16 +468,14 @@ SELECT done();
     </para>
 
     <para>
-    <literal>spi_cursor_close</literal> can be used to abort sequence of
-    <literal>spi_fetchrow</literal> calls. Normally, the call to
-    <literal>spi_fetchrow</literal> that returns <literal>undef</literal> is
-    the signal that there are no more rows to read. Also
-    that call automatically frees the cursor associated with the query. If it is desired not
-    to read all retuned rows, <literal>spi_cursor_close</literal> must be
-    called to avoid memory leaks.  
+     Normally, <function>spi_fetchrow</> should be repeated until it
+     returns <literal>undef</literal>, indicating that there are no more
+     rows to read.  The cursor is automatically freed when
+     <function>spi_fetchrow</> returns <literal>undef</literal>.
+     If you do not wish to read all the rows, instead call
+     <function>spi_cursor_close</> to free the cursor.
+     Failure to do so will result in memory leaks.
     </para>
-
-
      </listitem>
     </varlistentry>
 
@@ -630,8 +618,8 @@ CREATE FUNCTION badfunc() RETURNS integer AS $$
     return 1;
 $$ LANGUAGE plperl;
 </programlisting>
-       The creation of this function will fail as its use of a forbidden
-       operation will be be caught by the validator.
+    The creation of this function will fail as its use of a forbidden
+    operation will be be caught by the validator.
   </para>
 
   <para>
@@ -748,8 +736,8 @@ $$ LANGUAGE plperl;
      <listitem>
       <para>
        Name of the table on which the trigger fired. This has been deprecated,
-          and could be removed in a future release. 
-          Please use $_TD-&gt;{table_name} instead.
+       and could be removed in a future release. 
+       Please use $_TD-&gt;{table_name} instead.
       </para>
      </listitem>
     </varlistentry>
index ac5b2b4cfc60271841ea9ae9c49721657140d8be..fb2fe735a6c0e61d785b44f9f125d46a3b2dc5eb 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $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"> 
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -2040,9 +2040,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
      The <replaceable>target</replaceable> is a record variable, row variable,
      or comma-separated list of scalar variables.
      The <replaceable>target</replaceable> is successively assigned each row
-     resulting from the <replaceable>query</replaceable> (which must be a
-     <command>SELECT</command> command) and the loop body is executed for each
-     row. Here is an example:
+     resulting from the <replaceable>query</replaceable> and the loop body is
+     executed for each row. Here is an example:
 <programlisting>
 CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
 DECLARE
@@ -2069,6 +2068,15 @@ $$ LANGUAGE plpgsql;
      assigned row value is still accessible after the loop.
     </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>
      The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
      rows:
@@ -2078,12 +2086,11 @@ FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</r
     <replaceable>statements</replaceable>
 END LOOP <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
-     This is like the previous form, except that the source
-     <command>SELECT</command> statement is specified as a string
-     expression, which is evaluated and replanned on each entry to
-     the <literal>FOR</> loop.  This allows the programmer to choose the speed of
-     a preplanned query or the flexibility of a dynamic query, just
-     as with a plain <command>EXECUTE</command> statement.
+     This is like the previous form, except that the source query
+     is specified as a string expression, which is evaluated and replanned
+     on each entry to the <literal>FOR</> loop.  This allows the programmer to
+     choose the speed of a preplanned query or the flexibility of a dynamic
+     query, just as with a plain <command>EXECUTE</command> statement.
     </para>
 
     <note>
index 4430b3e4c7132211ba1cec7a450a969743e4debc..016874dce709b283c261ea8acd5811901dc2694f 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $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">
  <title>Server Programming Interface</title>
@@ -535,15 +535,15 @@ typedef struct
      <term><symbol>SPI_ERROR_TRANSACTION</symbol></term>
      <listitem>
       <para>
-          if any command involving transaction manipulation was attempted
-          (<command>BEGIN</>,
-          <command>COMMIT</>,
-          <command>ROLLBACK</>,
-          <command>SAVEPOINT</>,
-          <command>PREPARE TRANSACTION</>,
-          <command>COMMIT PREPARED</>,
-          <command>ROLLBACK PREPARED</>,
-          or any variant thereof)
+       if any command involving transaction manipulation was attempted
+       (<command>BEGIN</>,
+       <command>COMMIT</>,
+       <command>ROLLBACK</>,
+       <command>SAVEPOINT</>,
+       <command>PREPARE TRANSACTION</>,
+       <command>COMMIT PREPARED</>,
+       <command>ROLLBACK PREPARED</>,
+       or any variant thereof)
       </para>
      </listitem>
     </varlistentry>
@@ -917,10 +917,12 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
   <para>
    <function>SPI_is_cursor_plan</function> returns <symbol>true</symbol>
    if a plan prepared by <function>SPI_prepare</function> can be passed
-   as an argument to <function>SPI_cursor_open</function> and <symbol>
-   false</symbol> if that is not the case. The criteria are that the
+   as an argument to <function>SPI_cursor_open</function>, or
+   <symbol>false</symbol> if that is not the case. The criteria are that the
    <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.
   </para>
  </refsect1>
index 8b9dd99d4e4ff428cdcbced65fd327fe32721d79..0685426a2f25c7e5005f05615796b5d365aa1c10 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * 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)
 
        if (query->into)
                ereport(ERROR,
-                               (errcode(ERRCODE_SYNTAX_ERROR),
+                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
                                 errmsg("DECLARE CURSOR may not specify INTO")));
        if (query->rowMarks != NIL)
                ereport(ERROR,
index 05c4a542b842fd59dab15624b8c73a69b7654830..4b8a166ffae2b328373523f51702324114f5bad0 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * 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)
        /*
         * if there is a result relation, initialize result relation stuff
         */
-       if (parseTree->resultRelation != 0 && operation != CMD_SELECT)
+       if (parseTree->resultRelation)
        {
                List       *resultRelations = parseTree->resultRelations;
                int                     numResultRelations;
index 0da2abba77cbd300198bf707aa5f766cca39c1b6..28462ba8b8b09bbe6e831fc24917927e6bd6a545 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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)
                         * run it to completion.  (If we run to completion then
                         * 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;
                        else
                                count = 0L;
@@ -868,7 +870,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                                        JunkFilter **junkFilter)
 {
        Query      *parse;
-       int                     cmd;
+       bool            isSelect;
        List       *tlist;
        ListCell   *tlistitem;
        int                     tlistlen;
@@ -893,15 +895,18 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
        /* find the final query */
        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.
         */
        if (rettype == VOIDOID)
        {
-               if (cmd == CMD_SELECT)
+               if (isSelect)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                         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,
        }
 
        /* by here, the function is declared to return some type */
-       if (cmd != CMD_SELECT)
+       if (!isSelect)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                         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,
        /*
         * Count the non-junk entries in the result targetlist.
         */
+       tlist = parse->targetList;
        tlistlen = ExecCleanTargetListLength(tlist);
 
        fn_typtype = get_typtype(rettype);
index c1f39f7c4f7682d8894377332c0894c470ffc0ed..6c4cc5854603624d575f5dff2c6c770703a33a6d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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,
        planTree = (Plan *) linitial(ptlist);
 
        /* Must be a query that returns tuples */
-       switch (queryTree->commandType)
-       {
-               case CMD_SELECT:
-                       if (queryTree->into != NULL)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-                                                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;
-       }
+       if (!QueryReturnsTuples(queryTree))
+               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))));
 
        /* Reset SPI result (note we deliberately don't touch lastoid) */
        SPI_processed = 0;
@@ -876,7 +862,7 @@ SPI_cursor_open(const char *name, void *plan,
                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));
        queryTree = copyObject(queryTree);
        planTree = copyObject(planTree);
@@ -919,9 +905,9 @@ SPI_cursor_open(const char *name, void *plan,
         * Set up the portal.
         */
        PortalDefineQuery(portal,
-                                         NULL,
+                                         NULL,         /* no statement name */
                                          spiplan->query,
-                                         "SELECT", /* don't have the raw parse tree... */
+                                         CreateQueryTag(queryTree),
                                          list_make1(queryTree),
                                          list_make1(planTree),
                                          PortalGetHeapMemory(portal));
@@ -954,9 +940,16 @@ SPI_cursor_open(const char *name, void *plan,
         */
        PortalStart(portal, paramLI, snapshot);
 
-       Assert(portal->strategy == PORTAL_ONE_SELECT ||
-                  portal->strategy == PORTAL_ONE_RETURNING ||
-                  portal->strategy == PORTAL_UTIL_SELECT);
+       /*
+        * If this test fails then we're out of sync with pquery.c about
+        * 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 portal;
@@ -1046,12 +1039,12 @@ SPI_getargcount(void *plan)
 
 /*
  * Returns true if the plan contains exactly one command
- * and that command originates from normal SELECT (i.e.
- * *not* a SELECT ... INTO). In essence, the result indicates
- * if the command can be used with SPI_cursor_open
+ * and that command returns tuples to the caller (eg, SELECT or
+ * INSERT ... RETURNING, but not SELECT ... INTO). In essence,
+ * the result indicates if the command can be used with SPI_cursor_open
  *
  * Parameters
- *       plan A plan previously prepared using SPI_prepare
+ *       plan: A plan previously prepared using SPI_prepare
  */
 bool
 SPI_is_cursor_plan(void *plan)
@@ -1070,7 +1063,7 @@ SPI_is_cursor_plan(void *plan)
        {
                Query      *queryTree = (Query *) linitial((List *) linitial(qtlist));
 
-               if (queryTree->commandType == CMD_SELECT && queryTree->into == NULL)
+               if (QueryReturnsTuples(queryTree))
                        return true;
        }
        return false;
index ad128605dccd7c3fdaab163e890d25508adb3688..ef92d9d3c9f33cb54e013f583dfa6d4849f90001 100644 (file)
@@ -15,7 +15,7 @@
  *
  *
  * 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)
         */
        if (!IsA(subquery, Query) ||
                subquery->commandType != CMD_SELECT ||
-               subquery->resultRelation != 0 ||
                subquery->into != NULL)
                elog(ERROR, "subquery is bogus");
 
@@ -686,7 +685,6 @@ 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->resultRelation != 0 ||
                subquery->into != NULL)
                elog(ERROR, "subquery is bogus");
 
index 04f346d46256ee15573cbbc283b6d774ed295a36..71e727a7b4c1a602ed36f2e76480cc36b44fe12f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -2689,7 +2689,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
         */
        if (!IsA(querytree, Query) ||
                querytree->commandType != CMD_SELECT ||
-               querytree->resultRelation != 0 ||
                querytree->into ||
                querytree->hasAggs ||
                querytree->hasSubLinks ||
index 61242ef712e2ea548dcece0cb4b0d32a05db7a25..39c7372733f3b4dae970620670f4275f56231267 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * 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)
        /* 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(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;
 }
index d34529c74ee7d30f70be6f05f0b665357e83d8da..021fb3fa553f586f5e059c0f908a717638e090cc 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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)
 
        if (query->commandType != CMD_SELECT)
                elog(ERROR, "expected SELECT query from subquery in FROM");
-       if (query->resultRelation != 0 || query->into != NULL)
+       if (query->into != NULL)
                ereport(ERROR,
                                (errcode(ERRCODE_SYNTAX_ERROR),
                                 errmsg("subquery in FROM may not have SELECT INTO")));
index 6a7117e98bfa705e33206871554d0178ab9e33af..0812c3d441848307ee8193ce39186c61346b9042 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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)
                elog(ERROR, "bad query in sub-select");
        qtree = (Query *) linitial(qtrees);
        if (qtree->commandType != CMD_SELECT ||
-               qtree->resultRelation != 0)
+               qtree->into != NULL)
                elog(ERROR, "bad query in sub-select");
        sublink->subselect = (Node *) qtree;
 
index 3e1f65d86817436c3b53d3a103dec75fe5e36740..df24c6751c2c8b79095ae0df29ec84d21a4b6c2f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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)
                 * ... the one action must be a SELECT, ...
                 */
                query = (Query *) linitial(action);
-               if (!is_instead || query->commandType != CMD_SELECT)
+               if (!is_instead ||
+                       query->commandType != CMD_SELECT || query->into != NULL)
                        ereport(ERROR,
                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("rules on SELECT must have action INSTEAD SELECT")));
index 807b64360c8e614aa878dc60bf6f508d749c9719..7d6941ddcef93aa57f389711ae122666e8a63a09 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * 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)
 }
 
 
+/*
+ * 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
  *             utility to get a string representation of the
index f0f40e002e151c27e16ca466131df91aaba1a6a0..222f5d26cc3800c8ba37297382c52ad6e29345c9 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * 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
        Node       *utilityStmt;        /* non-null if this is a non-optimizable
                                                                 * 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 */
        List       *intoOptions;                /* options from WITH clause */
index 09f96a262c7a610132a5062c4b5befd604741c17..6674dec129efd4be96851e8c086f62d83072c5b6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * 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);
 
 extern const char *CreateQueryTag(Query *parsetree);
 
+extern bool QueryReturnsTuples(Query *parsetree);
+
 extern bool QueryIsReadOnly(Query *parsetree);
 
 extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);