I have developed a patch that allows declaring cursors over prepared
statements:

    DECLARE cursor_name CURSOR FOR prepared_statement_name
                                   [ USING param, param, ... ]

This is an SQL standard feature.  ECPG already supports it (with
different internals).

Internally, this just connects existing functionality in different ways,
so it doesn't really introduce anything new.

One point worth pondering is how to pass the parameters of the prepared
statements.  The actual SQL standard syntax would be

    DECLARE cursor_name CURSOR FOR prepared_statement_name;
    OPEN cursor_name USING param, param;

But since we don't have the OPEN statement in direct SQL, it made sense
to me to attach the USING clause directly to the DECLARE statement.

Curiously, the direct EXECUTE statement uses the non-standard syntax

    EXECUTE prep_stmt (param, param);

instead of the standard

    EXECUTE prep_stmt USING param, param;

I tried to consolidate this.  But using

    DECLARE c CURSOR FOR p (foo, bar)

leads to parsing conflicts (and looks confusing?), and instead allowing
EXECUTE + USING leads to a mess in the ECPG parser that exhausted me.
So I'm leaving it as is for now and might give supporting EXECUTE +
USING another try later on.

When looking at the patch, some parts will look easier through git diff -w.

And the changes in the ECPG parser are needed because ECPG already
supported that syntax separately, but now it needs to override the rules
from the main parser instead.  That stuff has test coverage, fortunately.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From c1e8ecf95599a9085e5f16bcd4aab3f13a2d6800 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@gmx.net>
Date: Thu, 7 Jun 2018 11:46:16 -0400
Subject: [PATCH] Cursors over prepared statements

Add command variant

    DECLARE cursor_name CURSOR FOR prepared_statement_name [ USING param, 
param, ... ]

to open a cursor over a previously defined prepared statement.
---
 doc/src/sgml/ref/declare.sgml              |  37 +++++++
 src/backend/commands/portalcmds.c          | 109 +++++++++++++++------
 src/backend/commands/prepare.c             |  16 ++-
 src/backend/parser/analyze.c               |   2 +-
 src/backend/parser/gram.y                  |  24 +++++
 src/include/commands/prepare.h             |   3 +
 src/interfaces/ecpg/preproc/check_rules.pl |   3 +
 src/interfaces/ecpg/preproc/ecpg.addons    |  63 +++++++++++-
 src/interfaces/ecpg/preproc/ecpg.trailer   |  65 ------------
 src/interfaces/ecpg/preproc/ecpg.type      |   1 -
 src/interfaces/ecpg/preproc/parse.pl       |   2 +
 src/test/regress/expected/portals.out      |  54 ++++++++++
 src/test/regress/sql/portals.sql           |  40 ++++++++
 13 files changed, 309 insertions(+), 110 deletions(-)

diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml
index 34ca9df243..2b127bdd6a 100644
--- a/doc/src/sgml/ref/declare.sgml
+++ b/doc/src/sgml/ref/declare.sgml
@@ -28,6 +28,9 @@
 <synopsis>
 DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ 
INSENSITIVE ] [ [ NO ] SCROLL ]
     CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable 
class="parameter">query</replaceable>
+
+DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [ 
INSENSITIVE ] [ [ NO ] SCROLL ]
+    CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable 
class="parameter">prepared_statement</replaceable> [ USING <replaceable 
class="parameter">parameter</replaceable> [, ...] ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -130,6 +133,31 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><replaceable 
class="parameter">prepared_statement</replaceable></term>
+    <listitem>
+     <para>
+      The name of the prepared statement (created with <xref
+      linkend="sql-prepare"/>) which will provide the rows to be returned by
+      the cursor.  The prepared statement is restricted to contain the same
+      kinds of statements as mentioned under <replaceable
+      class="parameter">query</replaceable> above.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">parameter</replaceable></term>
+    <listitem>
+     <para>
+      The actual value of a parameter to the prepared statement.  This
+      must be an expression yielding a value that is compatible with
+      the data type of this parameter, as was determined when the
+      prepared statement was created.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
 
   <para>
@@ -313,6 +341,14 @@ <title>Examples</title>
    See <xref linkend="sql-fetch"/> for more
    examples of cursor usage.
   </para>
+
+  <para>
+   To declare a cursor via a prepared statement:
+<programlisting>
+PREPARE p1 AS SELECT name, price FROM produce WHERE color = $1;
+DECLARE c2 CURSOR FOR p1 USING 'green';
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
@@ -343,6 +379,7 @@ <title>See Also</title>
 
   <simplelist type="inline">
    <member><xref linkend="sql-close"/></member>
+   <member><xref linkend="sql-execute"/></member>
    <member><xref linkend="sql-fetch"/></member>
    <member><xref linkend="sql-move"/></member>
   </simplelist>
diff --git a/src/backend/commands/portalcmds.c 
b/src/backend/commands/portalcmds.c
index 568499761f..6c5b274b51 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -25,6 +25,7 @@
 
 #include "access/xact.h"
 #include "commands/portalcmds.h"
+#include "commands/prepare.h"
 #include "executor/executor.h"
 #include "executor/tstoreReceiver.h"
 #include "rewrite/rewriteHandler.h"
@@ -44,9 +45,13 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo 
params,
 {
        Query      *query = castNode(Query, cstmt->query);
        List       *rewritten;
-       PlannedStmt *plan;
+       PlannedStmt *plan = NULL;
+       PreparedStatement *prepstmt = NULL;
+       ParamListInfo paramLI = NULL;
+       EState     *estate = NULL;
        Portal          portal;
        MemoryContext oldContext;
+       CachedPlan *cplan = NULL;
 
        /*
         * Disallow empty-string cursor name (conflicts with protocol-level
@@ -65,31 +70,61 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo 
params,
        if (!(cstmt->options & CURSOR_OPT_HOLD))
                RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
 
-       /*
-        * Parse analysis was done already, but we still have to run the rule
-        * rewriter.  We do not do AcquireRewriteLocks: we assume the query 
either
-        * came straight from the parser, or suitable locks were acquired by
-        * plancache.c.
-        *
-        * Because the rewriter and planner tend to scribble on the input, we 
make
-        * a preliminary copy of the source querytree.  This prevents problems 
in
-        * the case that the DECLARE CURSOR is in a portal or plpgsql function 
and
-        * is executed repeatedly.  (See also the same hack in EXPLAIN and
-        * PREPARE.)  XXX FIXME someday.
-        */
-       rewritten = QueryRewrite((Query *) copyObject(query));
+       if (query->commandType == CMD_SELECT)
+       {
+               /*
+                * Parse analysis was done already, but we still have to run 
the rule
+                * rewriter.  We do not do AcquireRewriteLocks: we assume the 
query either
+                * came straight from the parser, or suitable locks were 
acquired by
+                * plancache.c.
+                *
+                * Because the rewriter and planner tend to scribble on the 
input, we make
+                * a preliminary copy of the source querytree.  This prevents 
problems in
+                * the case that the DECLARE CURSOR is in a portal or plpgsql 
function and
+                * is executed repeatedly.  (See also the same hack in EXPLAIN 
and
+                * PREPARE.)  XXX FIXME someday.
+                */
+               rewritten = QueryRewrite((Query *) copyObject(query));
 
-       /* SELECT should never rewrite to more or less than one query */
-       if (list_length(rewritten) != 1)
-               elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
+               /* SELECT should never rewrite to more or less than one query */
+               if (list_length(rewritten) != 1)
+                       elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
 
-       query = linitial_node(Query, rewritten);
+               query = linitial_node(Query, rewritten);
 
-       if (query->commandType != CMD_SELECT)
-               elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
+               if (query->commandType != CMD_SELECT)
+                       elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
 
-       /* Plan the query, applying the specified options */
-       plan = pg_plan_query(query, cstmt->options, params);
+               /* Plan the query, applying the specified options */
+               plan = pg_plan_query(query, cstmt->options, params);
+       }
+       else if (query->commandType == CMD_UTILITY)
+       {
+               ExecuteStmt *es = castNode(ExecuteStmt, query->utilityStmt);
+               PlannedStmt *pstmt;
+
+               prepstmt = FetchPreparedStatement(es->name, true);
+
+               if (prepstmt->plansource->num_params > 0)
+               {
+                       estate = CreateExecutorState();
+                       estate->es_param_list_info = params;
+                       paramLI = PreparedStatementEvaluateParams(prepstmt, 
es->params,
+                                                                               
                          queryString, estate);
+               }
+
+               cplan = GetCachedPlan(prepstmt->plansource, paramLI, false, 
NULL);
+
+               if (list_length(cplan->stmt_list) != 1)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("prepared statement is not a 
SELECT")));
+               pstmt = linitial_node(PlannedStmt, cplan->stmt_list);
+               if (pstmt->commandType != CMD_SELECT)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("prepared statement is not a 
SELECT")));
+       }
 
        /*
         * Create a portal and copy the plan and queryString into its memory.
@@ -98,16 +133,30 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo 
params,
 
        oldContext = MemoryContextSwitchTo(portal->portalContext);
 
-       plan = copyObject(plan);
-
        queryString = pstrdup(queryString);
 
-       PortalDefineQuery(portal,
-                                         NULL,
-                                         queryString,
-                                         "SELECT", /* cursor's query is always 
a SELECT */
-                                         list_make1(plan),
-                                         NULL);
+       if (plan)
+       {
+               plan = copyObject(plan);
+
+               PortalDefineQuery(portal,
+                                                 NULL,
+                                                 queryString,
+                                                 "SELECT", /* cursor's query 
is always a SELECT */
+                                                 list_make1(plan),
+                                                 NULL);
+       }
+       else
+       {
+               PortalDefineQuery(portal,
+                                                 NULL,
+                                                 queryString,
+                                                 
prepstmt->plansource->commandTag,
+                                                 cplan->stmt_list,
+                                                 cplan);
+
+               plan = linitial(cplan->stmt_list);
+       }
 
        /*----------
         * Also copy the outer portal's parameter list into the inner portal's
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..a4a6626654 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -46,8 +46,6 @@
 static HTAB *prepared_queries = NULL;
 
 static void InitQueryHashTable(void);
-static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
-                          const char *queryString, EState *estate);
 static Datum build_regtype_array(Oid *param_types, int num_params);
 
 /*
@@ -229,8 +227,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
                 */
                estate = CreateExecutorState();
                estate->es_param_list_info = params;
-               paramLI = EvaluateParams(entry, stmt->params,
-                                                                queryString, 
estate);
+               paramLI = PreparedStatementEvaluateParams(entry, stmt->params,
+                                                                               
                  queryString, estate);
        }
 
        /* Create a new portal to run the query in */
@@ -312,7 +310,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
 }
 
 /*
- * EvaluateParams: evaluate a list of parameters.
+ * PreparedStatementEvaluateParams: evaluate a list of parameters.
  *
  * pstmt: statement we are getting parameters for.
  * params: list of given parameter expressions (raw parser output!)
@@ -323,8 +321,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause,
  * CreateQueryDesc(), which allows the executor to make use of the parameters
  * during query execution.
  */
-static ParamListInfo
-EvaluateParams(PreparedStatement *pstmt, List *params,
+ParamListInfo
+PreparedStatementEvaluateParams(PreparedStatement *pstmt, List *params,
                           const char *queryString, EState *estate)
 {
        Oid                *param_types = pstmt->plansource->param_types;
@@ -665,8 +663,8 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause 
*into, ExplainState *es,
                 */
                estate = CreateExecutorState();
                estate->es_param_list_info = params;
-               paramLI = EvaluateParams(entry, execstmt->params,
-                                                                queryString, 
estate);
+               paramLI = PreparedStatementEvaluateParams(entry, 
execstmt->params,
+                                                                               
                  queryString, estate);
        }
 
        /* Replan if needed, and acquire a transient refcount */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 05f57591e4..f6d0753dd1 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2412,7 +2412,7 @@ transformDeclareCursorStmt(ParseState *pstate, 
DeclareCursorStmt *stmt)
 
        /* Grammar should not have allowed anything but SELECT */
        if (!IsA(query, Query) ||
-               query->commandType != CMD_SELECT)
+               (query->commandType != CMD_SELECT && query->commandType != 
CMD_UTILITY))
                elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
 
        /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90dfac2cb1..2639174b26 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11125,6 +11125,30 @@ DeclareCursorStmt: DECLARE cursor_name cursor_options 
CURSOR opt_hold FOR Select
                                        n->query = $7;
                                        $$ = (Node *)n;
                                }
+                       | DECLARE cursor_name cursor_options CURSOR opt_hold 
FOR name
+                               {
+                                       DeclareCursorStmt *n = 
makeNode(DeclareCursorStmt);
+                                       ExecuteStmt *es = makeNode(ExecuteStmt);
+
+                                       n->portalname = $2;
+                                       n->options = $3 | $5 | 
CURSOR_OPT_FAST_PLAN;
+                                       es->name = $7;
+                                       es->params = NIL;
+                                       n->query = (Node *)es;
+                                       $$ = (Node *)n;
+                               }
+                       | DECLARE cursor_name cursor_options CURSOR opt_hold 
FOR name USING expr_list
+                               {
+                                       DeclareCursorStmt *n = 
makeNode(DeclareCursorStmt);
+                                       ExecuteStmt *es = makeNode(ExecuteStmt);
+
+                                       n->portalname = $2;
+                                       n->options = $3 | $5 | 
CURSOR_OPT_FAST_PLAN;
+                                       es->name = $7;
+                                       es->params = $9;
+                                       n->query = (Node *)es;
+                                       $$ = (Node *)n;
+                               }
                ;
 
 cursor_name:   name                                            { $$ = $1; }
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index ffec029df4..3691170120 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -55,6 +55,9 @@ extern void DropPreparedStatement(const char *stmt_name, bool 
showError);
 extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
 extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
 
+extern ParamListInfo PreparedStatementEvaluateParams(PreparedStatement *pstmt, 
List *params,
+                                                                               
                         const char *queryString, EState *estate);
+
 extern void DropAllPreparedStatements(void);
 
 #endif                                                 /* PREPARE_H */
diff --git a/src/interfaces/ecpg/preproc/check_rules.pl 
b/src/interfaces/ecpg/preproc/check_rules.pl
index 6c8b004854..b0cdce7c19 100644
--- a/src/interfaces/ecpg/preproc/check_rules.pl
+++ b/src/interfaces/ecpg/preproc/check_rules.pl
@@ -36,6 +36,9 @@
 }
 
 my %replace_line = (
+       
'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORname' =>
+         'DECLARE cursor_name cursor_options CURSOR opt_hold FOR 
prepared_name',
+
        'ExecuteStmtEXECUTEnameexecute_param_clause' =>
          'EXECUTE prepared_name execute_param_clause execute_rest',
 
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons 
b/src/interfaces/ecpg/preproc/ecpg.addons
index ca3efadc48..922d1fffd2 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -63,10 +63,6 @@ ECPG: stmtViewStmt rule
                whenever_action(2);
                free($1);
        }
-       | ECPGCursorStmt
-       {
-               output_simple_statement($1);
-       }
        | ECPGDeallocateDescr
        {
                fprintf(base_yyout,"ECPGdeallocate_desc(__LINE__, %s);",$1);
@@ -334,6 +330,65 @@ ECPG: 
DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORSelectSt
                else
                        $$ = cat2_str(adjust_outofscope_cursor_vars(this), 
comment);
        }
+ECPG: 
DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORprepared_name 
block
+               {
+                       struct cursor *ptr, *this;
+                       char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : 
mm_strdup($2);
+                       int (* strcmp_fn)(const char *, const char *) = (($2[0] 
== ':' || $2[0] == '"') ? strcmp : pg_strcasecmp);
+                       struct variable *thisquery = (struct variable 
*)mm_alloc(sizeof(struct variable));
+                       const char *con = connection ? connection : "NULL";
+                       char *comment;
+
+                       for (ptr = cur; ptr != NULL; ptr = ptr->next)
+                       {
+                               if (strcmp_fn($2, ptr->name) == 0)
+                               {
+                                       /* re-definition is a bug */
+                                       if ($2[0] == ':')
+                                               mmerror(PARSE_ERROR, ET_ERROR, 
"using variable \"%s\" in different declare statements is not supported", $2+1);
+                                       else
+                                               mmerror(PARSE_ERROR, ET_ERROR, 
"cursor \"%s\" is already defined", $2);
+                               }
+                       }
+
+                       this = (struct cursor *) mm_alloc(sizeof(struct 
cursor));
+
+                       /* initial definition */
+                       this->next = cur;
+                       this->name = $2;
+                       this->function = (current_function ? 
mm_strdup(current_function) : NULL);
+                       this->connection = connection;
+                       this->command =  cat_str(6, mm_strdup("declare"), 
cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for $1"));
+                       this->argsresult = NULL;
+                       this->argsresult_oos = NULL;
+
+                       thisquery->type = &ecpg_query;
+                       thisquery->brace_level = 0;
+                       thisquery->next = NULL;
+                       thisquery->name = (char *) 
mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + 
strlen($7));
+                       sprintf(thisquery->name, "ECPGprepared_statement(%s, 
%s, __LINE__)", con, $7);
+
+                       this->argsinsert = NULL;
+                       this->argsinsert_oos = NULL;
+                       if ($2[0] == ':')
+                       {
+                               struct variable *var = find_variable($2 + 1);
+                               remove_variable_from_list(&argsinsert, var);
+                               add_variable_to_head(&(this->argsinsert), var, 
&no_indicator);
+                       }
+                       add_variable_to_head(&(this->argsinsert), thisquery, 
&no_indicator);
+
+                       cur = this;
+
+                       comment = cat_str(3, mm_strdup("/*"), 
mm_strdup(this->command), mm_strdup("*/"));
+
+                       if ((braces_open > 0) && INFORMIX_MODE) /* we're in a 
function */
+                               $$ = cat_str(3, 
adjust_outofscope_cursor_vars(this),
+                                       
mm_strdup("ECPG_informix_reset_sqlca();"),
+                                       comment);
+                       else
+                               $$ = 
cat2_str(adjust_outofscope_cursor_vars(this), comment);
+               }
 ECPG: ClosePortalStmtCLOSEcursor_name block
        {
                char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : $2;
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer 
b/src/interfaces/ecpg/preproc/ecpg.trailer
index 19dc781885..d3123742eb 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -283,71 +283,6 @@ prepared_name: name
                | char_variable { $$ = $1; }
                ;
 
-/*
- * Declare a prepared cursor. The syntax is different from the standard
- * declare statement, so we create a new rule.
- */
-ECPGCursorStmt:  DECLARE cursor_name cursor_options CURSOR opt_hold FOR 
prepared_name
-               {
-                       struct cursor *ptr, *this;
-                       char *cursor_marker = $2[0] == ':' ? mm_strdup("$0") : 
mm_strdup($2);
-                       int (* strcmp_fn)(const char *, const char *) = (($2[0] 
== ':' || $2[0] == '"') ? strcmp : pg_strcasecmp);
-                       struct variable *thisquery = (struct variable 
*)mm_alloc(sizeof(struct variable));
-                       const char *con = connection ? connection : "NULL";
-                       char *comment;
-
-                       for (ptr = cur; ptr != NULL; ptr = ptr->next)
-                       {
-                               if (strcmp_fn($2, ptr->name) == 0)
-                               {
-                                       /* re-definition is a bug */
-                                       if ($2[0] == ':')
-                                               mmerror(PARSE_ERROR, ET_ERROR, 
"using variable \"%s\" in different declare statements is not supported", $2+1);
-                                       else
-                                               mmerror(PARSE_ERROR, ET_ERROR, 
"cursor \"%s\" is already defined", $2);
-                               }
-                       }
-
-                       this = (struct cursor *) mm_alloc(sizeof(struct 
cursor));
-
-                       /* initial definition */
-                       this->next = cur;
-                       this->name = $2;
-                       this->function = (current_function ? 
mm_strdup(current_function) : NULL);
-                       this->connection = connection;
-                       this->command =  cat_str(6, mm_strdup("declare"), 
cursor_marker, $3, mm_strdup("cursor"), $5, mm_strdup("for $1"));
-                       this->argsresult = NULL;
-                       this->argsresult_oos = NULL;
-
-                       thisquery->type = &ecpg_query;
-                       thisquery->brace_level = 0;
-                       thisquery->next = NULL;
-                       thisquery->name = (char *) 
mm_alloc(sizeof("ECPGprepared_statement(, , __LINE__)") + strlen(con) + 
strlen($7));
-                       sprintf(thisquery->name, "ECPGprepared_statement(%s, 
%s, __LINE__)", con, $7);
-
-                       this->argsinsert = NULL;
-                       this->argsinsert_oos = NULL;
-                       if ($2[0] == ':')
-                       {
-                               struct variable *var = find_variable($2 + 1);
-                               remove_variable_from_list(&argsinsert, var);
-                               add_variable_to_head(&(this->argsinsert), var, 
&no_indicator);
-                       }
-                       add_variable_to_head(&(this->argsinsert), thisquery, 
&no_indicator);
-
-                       cur = this;
-
-                       comment = cat_str(3, mm_strdup("/*"), 
mm_strdup(this->command), mm_strdup("*/"));
-
-                       if ((braces_open > 0) && INFORMIX_MODE) /* we're in a 
function */
-                               $$ = cat_str(3, 
adjust_outofscope_cursor_vars(this),
-                                       
mm_strdup("ECPG_informix_reset_sqlca();"),
-                                       comment);
-                       else
-                               $$ = 
cat2_str(adjust_outofscope_cursor_vars(this), comment);
-               }
-               ;
-
 ECPGExecuteImmediateStmt: EXECUTE IMMEDIATE execstring
                        {
                          /* execute immediate means prepare the statement and
diff --git a/src/interfaces/ecpg/preproc/ecpg.type 
b/src/interfaces/ecpg/preproc/ecpg.type
index 9497b91b9d..fab5b2d73a 100644
--- a/src/interfaces/ecpg/preproc/ecpg.type
+++ b/src/interfaces/ecpg/preproc/ecpg.type
@@ -5,7 +5,6 @@
 %type <str> ECPGColLabel
 %type <str> ECPGColLabelCommon
 %type <str> ECPGConnect
-%type <str> ECPGCursorStmt
 %type <str> ECPGDeallocateDescr
 %type <str> ECPGDeclaration
 %type <str> ECPGDeclare
diff --git a/src/interfaces/ecpg/preproc/parse.pl 
b/src/interfaces/ecpg/preproc/parse.pl
index b20383ab17..695118cbda 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -99,6 +99,8 @@
          'SHOW SESSION AUTHORIZATION ecpg_into',
        'returning_clauseRETURNINGtarget_list' =>
          'RETURNING target_list opt_ecpg_into',
+       
'DeclareCursorStmtDECLAREcursor_namecursor_optionsCURSORopt_holdFORname' =>
+         'DECLARE cursor_name cursor_options CURSOR opt_hold FOR 
prepared_name',
        'ExecuteStmtEXECUTEnameexecute_param_clause' =>
          'EXECUTE prepared_name execute_param_clause execute_rest',
        
'ExecuteStmtCREATEOptTempTABLEcreate_as_targetASEXECUTEnameexecute_param_clause'
diff --git a/src/test/regress/expected/portals.out 
b/src/test/regress/expected/portals.out
index 048b2fc3e3..72ffdceec7 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -1376,3 +1376,57 @@ fetch backward all in c2;
 (3 rows)
 
 rollback;
+-- cursors over prepared statements
+prepare foo as select generate_series(1,3) as g;
+begin;
+declare c1 cursor for foo;
+fetch all in c1;
+ g 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+rollback;
+deallocate foo;
+begin;
+declare c1 cursor for foo;
+ERROR:  prepared statement "foo" does not exist
+rollback;
+prepare foo1 (int, int) as select generate_series($1, $2);
+begin;
+declare c1 cursor for foo1 using 2, 4;
+fetch all in c1;
+ generate_series 
+-----------------
+               2
+               3
+               4
+(3 rows)
+
+rollback;
+begin;
+declare c1 cursor for foo1 using 3, 5;
+fetch all in c1;
+ generate_series 
+-----------------
+               3
+               4
+               5
+(3 rows)
+
+rollback;
+begin;
+declare c1 cursor for foo1 using 'foo', 'bar';
+ERROR:  invalid input syntax for integer: "foo"
+LINE 1: declare c1 cursor for foo1 using 'foo', 'bar';
+                                         ^
+rollback;
+CREATE TABLE cursor (a int);
+prepare foo2 as insert into cursor values ($1);
+begin;
+declare c1 cursor for foo2 using 1;
+ERROR:  prepared statement is not a SELECT
+rollback;
+DROP TABLE cursor;
diff --git a/src/test/regress/sql/portals.sql b/src/test/regress/sql/portals.sql
index d1a589094e..05891a47cf 100644
--- a/src/test/regress/sql/portals.sql
+++ b/src/test/regress/sql/portals.sql
@@ -510,3 +510,43 @@ CREATE TABLE cursor (a int);
 fetch all in c2;
 fetch backward all in c2;
 rollback;
+
+-- cursors over prepared statements
+prepare foo as select generate_series(1,3) as g;
+
+begin;
+declare c1 cursor for foo;
+fetch all in c1;
+rollback;
+
+deallocate foo;
+
+begin;
+declare c1 cursor for foo;
+rollback;
+
+prepare foo1 (int, int) as select generate_series($1, $2);
+
+begin;
+declare c1 cursor for foo1 using 2, 4;
+fetch all in c1;
+rollback;
+
+begin;
+declare c1 cursor for foo1 using 3, 5;
+fetch all in c1;
+rollback;
+
+begin;
+declare c1 cursor for foo1 using 'foo', 'bar';
+rollback;
+
+CREATE TABLE cursor (a int);
+
+prepare foo2 as insert into cursor values ($1);
+
+begin;
+declare c1 cursor for foo2 using 1;
+rollback;
+
+DROP TABLE cursor;

base-commit: 848b1f3e358f4a1bb98d8c4a07ff8ee5fd7ea9a0
-- 
2.17.1

Reply via email to